pax_global_header00006660000000000000000000000064144460274270014524gustar00rootroot0000000000000052 comment=e3fac057ed29ca485c2288a2788f62b73baec676 python-pytest-cov-4.1.0/000077500000000000000000000000001444602742700151625ustar00rootroot00000000000000python-pytest-cov-4.1.0/.bumpversion.cfg000066400000000000000000000011721444602742700202730ustar00rootroot00000000000000[bumpversion] current_version = 4.1.0 commit = True tag = True [bumpversion:file:setup.py] search = version='{current_version}' replace = version='{new_version}' [bumpversion:file (badge):README.rst] search = /v{current_version}.svg replace = /v{new_version}.svg [bumpversion:file (link):README.rst] search = /v{current_version}...master replace = /v{new_version}...master [bumpversion:file:docs/conf.py] search = version = release = '{current_version}' replace = version = release = '{new_version}' [bumpversion:file:src/pytest_cov/__init__.py] search = __version__ = '{current_version}' replace = __version__ = '{new_version}' python-pytest-cov-4.1.0/.cookiecutterrc000066400000000000000000000034111444602742700202070ustar00rootroot00000000000000# Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) default_context: allow_tests_inside_package: no appveyor: no c_extension_function: '-' c_extension_module: '-' c_extension_optional: no c_extension_support: no c_extension_test_pypi: no c_extension_test_pypi_username: '-' codacy: no codacy_projectid: '[Get ID from https://app.codacy.com/app/ionelmc/pytest-cov/settings]' codeclimate: no codecov: no command_line_interface: no command_line_interface_bin_name: '-' coveralls: no distribution_name: pytest-cov email: contact@ionelmc.ro full_name: Ionel Cristian Mărieș github_actions: yes legacy_python: yes license: MIT license linter: flake8 package_name: pytest_cov pre_commit: yes project_name: pytest-cov project_short_description: This plugin produces coverage reports. It supports centralised testing and distributed testing in both load and each modes. It also supports coverage of subprocesses. pypi_badge: yes pypi_disable_upload: no release_date: '2021-10-04' repo_hosting: github.com repo_hosting_domain: github.com repo_main_branch: master repo_name: pytest-cov repo_username: pytest-dev requiresio: yes scrutinizer: no setup_py_uses_setuptools_scm: no setup_py_uses_test_runner: no sphinx_docs: yes sphinx_docs_hosting: https://pytest-cov.readthedocs.io/ sphinx_doctest: no sphinx_theme: sphinx-py3doc-enhanced-theme test_matrix_configurator: no test_matrix_separate_coverage: no test_runner: pytest travis: no travis_osx: no version: 3.0.0 version_manager: bump2version website: http://blog.ionelmc.ro year_from: '2010' year_to: '2022' python-pytest-cov-4.1.0/.editorconfig000066400000000000000000000005411444602742700176370ustar00rootroot00000000000000# see https://editorconfig.org/ root = true [*] # Use Unix-style newlines for most files (except Windows files, see below). end_of_line = lf trim_trailing_whitespace = true indent_style = space insert_final_newline = true indent_size = 4 charset = utf-8 [*.{bat,cmd,ps1}] end_of_line = crlf [*.{yml,yaml}] indent_size = 2 [*.tsv] indent_style = tab python-pytest-cov-4.1.0/.github/000077500000000000000000000000001444602742700165225ustar00rootroot00000000000000python-pytest-cov-4.1.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001444602742700207055ustar00rootroot00000000000000python-pytest-cov-4.1.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000012071444602742700233770ustar00rootroot00000000000000--- name: 🐞 Bug report about: There a problem with how pytest-cov or coverage works --- # Summary ## Expected vs actual result # Reproducer ## Versions Output of relevant packages `pip list`, `python --version`, `pytest --version` etc. Make sure you include complete output of `tox` if you use it (it will show versions of various things). ## Config Include your `tox.ini`, `pytest.ini`, `.coveragerc`, `setup.cfg` or any relevant configuration. ## Code Link to your repository, gist, pastebin or just paste raw code that illustrates the issue. If you paste raw code make sure you quote it, eg: ```python def foobar(): pass ``` python-pytest-cov-4.1.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000006431444602742700244350ustar00rootroot00000000000000--- name: ✈ Feature request about: Proposal for a new feature in pytest-cov --- Before proposing please consider: * the maintenance cost of the feature * implementing it externally (like a shell/python script, pytest plugin or something else) # Summary These questions should be answered: * why is the feature needed? * what problem does it solve? * how it is better compared to past solutions to the problem? python-pytest-cov-4.1.0/.github/ISSUE_TEMPLATE/support_request.md000066400000000000000000000016521444602742700245170ustar00rootroot00000000000000--- name: 🤔 Support request about: Request help with setting up pytest-cov in your project --- Please go over all the sections and search https://pytest-cov.readthedocs.io/en/latest/ or https://coverage.readthedocs.io/en/latest/ before opening the issue. # Summary ## Expected vs actual result # Reproducer ## Versions Output of relevant packages `pip list`, `python --version`, `pytest --version` etc. Make sure you include complete output of `tox` if you use it (it will show versions of various things). ## Config Include your `tox.ini`, `pytest.ini`, `.coveragerc`, `setup.cfg` or any relevant configuration. ## Code Link to your repository, gist, pastebin or just paste raw code that illustrates the issue. If you paste raw code make sure you quote it, eg: ```python def foobar(): pass ``` # What has been tried to solve the problem You should outline the things you tried to solve the problem but didn't work. python-pytest-cov-4.1.0/.github/workflows/000077500000000000000000000000001444602742700205575ustar00rootroot00000000000000python-pytest-cov-4.1.0/.github/workflows/test.yml000066400000000000000000000207251444602742700222670ustar00rootroot00000000000000name: Tests on: [push, pull_request, workflow_dispatch] jobs: examples: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["pypy-3.9", "3.11"] target: [ "src-layout", "adhoc-layout", ] include: # Add new helper variables to existing jobs - {python-version: "pypy-3.9", tox-python-version: "pypy3"} - {python-version: "3.11", tox-python-version: "py311"} steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Cache uses: actions/cache@v3 with: path: ~/.cache/pip key: examples-v1-${{ hashFiles('**/tox.ini') }} restore-keys: | examples-v1- - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install --upgrade wheel python -m pip install --progress-bar=off tox -rci/requirements.txt - name: Examples run: | cd examples/${{ matrix.target }} tox -v -e ${{ matrix.tox-python-version }} test: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: fail-fast: false matrix: include: - name: 'check' python: '3.11' toxpython: 'python3.11' tox_env: 'check' os: 'ubuntu-latest' - name: 'docs' python: '3.11' toxpython: 'python3.11' tox_env: 'docs' os: 'ubuntu-latest' - name: 'py37-pytest73-xdist330-coverage72 (ubuntu)' python: '3.7' toxpython: 'python3.7' python_arch: 'x64' tox_env: 'py37-pytest73-xdist330-coverage72' os: 'ubuntu-latest' - name: 'py37-pytest73-xdist330-coverage72 (windows)' python: '3.7' toxpython: 'python3.7' python_arch: 'x64' tox_env: 'py37-pytest73-xdist330-coverage72' os: 'windows-latest' - name: 'py37-pytest73-xdist330-coverage72 (macos)' python: '3.7' toxpython: 'python3.7' python_arch: 'x64' tox_env: 'py37-pytest73-xdist330-coverage72' os: 'macos-latest' - name: 'py38-pytest73-xdist330-coverage72 (ubuntu)' python: '3.8' toxpython: 'python3.8' python_arch: 'x64' tox_env: 'py38-pytest73-xdist330-coverage72' os: 'ubuntu-latest' - name: 'py38-pytest73-xdist330-coverage72 (windows)' python: '3.8' toxpython: 'python3.8' python_arch: 'x64' tox_env: 'py38-pytest73-xdist330-coverage72' os: 'windows-latest' - name: 'py38-pytest73-xdist330-coverage72 (macos)' python: '3.8' toxpython: 'python3.8' python_arch: 'x64' tox_env: 'py38-pytest73-xdist330-coverage72' os: 'macos-latest' - name: 'py39-pytest73-xdist330-coverage72 (ubuntu)' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' tox_env: 'py39-pytest73-xdist330-coverage72' os: 'ubuntu-latest' - name: 'py39-pytest73-xdist330-coverage72 (windows)' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' tox_env: 'py39-pytest73-xdist330-coverage72' os: 'windows-latest' - name: 'py39-pytest73-xdist330-coverage72 (macos)' python: '3.9' toxpython: 'python3.9' python_arch: 'x64' tox_env: 'py39-pytest73-xdist330-coverage72' os: 'macos-latest' - name: 'py310-pytest73-xdist330-coverage72 (ubuntu)' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' tox_env: 'py310-pytest73-xdist330-coverage72' os: 'ubuntu-latest' - name: 'py310-pytest73-xdist330-coverage72 (windows)' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' tox_env: 'py310-pytest73-xdist330-coverage72' os: 'windows-latest' - name: 'py310-pytest73-xdist330-coverage72 (macos)' python: '3.10' toxpython: 'python3.10' python_arch: 'x64' tox_env: 'py310-pytest73-xdist330-coverage72' os: 'macos-latest' - name: 'py311-pytest73-xdist330-coverage72 (ubuntu)' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' tox_env: 'py311-pytest73-xdist330-coverage72' os: 'ubuntu-latest' - name: 'py311-pytest73-xdist330-coverage72 (windows)' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' tox_env: 'py311-pytest73-xdist330-coverage72' os: 'windows-latest' - name: 'py311-pytest73-xdist330-coverage72 (macos)' python: '3.11' toxpython: 'python3.11' python_arch: 'x64' tox_env: 'py311-pytest73-xdist330-coverage72' os: 'macos-latest' - name: 'pypy37-pytest73-xdist330-coverage72 (ubuntu)' python: 'pypy-3.7' toxpython: 'pypy3.7' python_arch: 'x64' tox_env: 'pypy37-pytest73-xdist330-coverage72' os: 'ubuntu-latest' - name: 'pypy37-pytest73-xdist330-coverage72 (windows)' python: 'pypy-3.7' toxpython: 'pypy3.7' python_arch: 'x64' tox_env: 'pypy37-pytest73-xdist330-coverage72' os: 'windows-latest' - name: 'pypy37-pytest73-xdist330-coverage72 (macos)' python: 'pypy-3.7' toxpython: 'pypy3.7' python_arch: 'x64' tox_env: 'pypy37-pytest73-xdist330-coverage72' os: 'macos-latest' - name: 'pypy38-pytest73-xdist330-coverage72 (ubuntu)' python: 'pypy-3.8' toxpython: 'pypy3.8' python_arch: 'x64' tox_env: 'pypy38-pytest73-xdist330-coverage72' os: 'ubuntu-latest' - name: 'pypy38-pytest73-xdist330-coverage72 (windows)' python: 'pypy-3.8' toxpython: 'pypy3.8' python_arch: 'x64' tox_env: 'pypy38-pytest73-xdist330-coverage72' os: 'windows-latest' - name: 'pypy38-pytest73-xdist330-coverage72 (macos)' python: 'pypy-3.8' toxpython: 'pypy3.8' python_arch: 'x64' tox_env: 'pypy38-pytest73-xdist330-coverage72' os: 'macos-latest' - name: 'pypy39-pytest73-xdist330-coverage72 (ubuntu)' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' tox_env: 'pypy39-pytest73-xdist330-coverage72' os: 'ubuntu-latest' - name: 'pypy39-pytest73-xdist330-coverage72 (windows)' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' tox_env: 'pypy39-pytest73-xdist330-coverage72' os: 'windows-latest' - name: 'pypy39-pytest73-xdist330-coverage72 (macos)' python: 'pypy-3.9' toxpython: 'pypy3.9' python_arch: 'x64' tox_env: 'pypy39-pytest73-xdist330-coverage72' os: 'macos-latest' steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} architecture: ${{ matrix.python_arch }} - name: install dependencies run: | python -mpip install --progress-bar=off -r ci/requirements.txt virtualenv --version pip --version tox --version pip list --format=freeze - name: test env: TOXPYTHON: '${{ matrix.toxpython }}' run: > tox -e ${{ matrix.tox_env }} -v successful: # this provides a single status check for branch merge rules # (use this in `Require status checks to pass before merging` in branch settings) if: always() needs: - test - examples runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} python-pytest-cov-4.1.0/.pre-commit-config.yaml000066400000000000000000000011751444602742700214470ustar00rootroot00000000000000# To install the git pre-commit hook run: # pre-commit install # To update the pre-commit hooks run: # pre-commit autoupdate repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer exclude: '.*\.pth$' - id: debug-statements - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort - repo: https://github.com/asottile/pyupgrade rev: v3.3.1 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 hooks: - id: flake8 python-pytest-cov-4.1.0/.readthedocs.yml000066400000000000000000000003641444602742700202530ustar00rootroot00000000000000# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 sphinx: configuration: docs/conf.py formats: all python: version: 3 install: - requirements: docs/requirements.txt - method: pip path: . python-pytest-cov-4.1.0/AUTHORS.rst000066400000000000000000000052661444602742700170520ustar00rootroot00000000000000Authors ======= * Marc Schlaich - http://www.schlamar.org * Rick van Hattem - http://wol.ph * Buck Evan - https://github.com/bukzor * Eric Larson - http://larsoner.com * Marc Abramowitz - http://marc-abramowitz.com * Thomas Kluyver - https://github.com/takluyver * Guillaume Ayoub - http://www.yabz.fr * Federico Ceratto - http://firelet.net * Josh Kalderimis - http://blog.cookiestack.com * Ionel Cristian Mărieș - https://blog.ionelmc.ro * Christian Ledermann - https://github.com/cleder * Alec Nikolas Reiter - https://github.com/justanr * Patrick Lannigan - https://github.com/plannigan * David Szotten - https://github.com/davidszotten * Michael Elovskikh - https://github.com/wronglink * Saurabh Kumar - https://github.com/theskumar * Michael Elovskikh - https://github.com/wronglink * Daniel Hahler - https://daniel.hahler.de * Florian Bruhin - http://www.the-compiler.org * Zoltan Kozma - https://github.com/kozmaz87 * Francis Niu - https://flniu.github.io * Jannis Leidel - https://github.com/jezdez * Ryan Hiebert - http://ryanhiebert.com/ * Terence Honles - https://github.com/terencehonles * Jeremy Bowman - https://github.com/jmbowman * Samuel Giffard - https://github.com/Mulugruntz * Семён Марьясин - https://github.com/MarSoft * Alexander Shadchin - https://github.com/shadchin * Thomas Grainger - https://graingert.co.uk * Juanjo Bazán - https://github.com/xuanxu * Andrew Murray - https://github.com/radarhere * Ned Batchelder - https://nedbatchelder.com/ * Albert Tugushev - https://github.com/atugushev * Martín Gaitán - https://github.com/mgaitan * Hugo van Kemenade - https://github.com/hugovk * Michael Manganiello - https://github.com/adamantike * Anders Hovmöller - https://github.com/boxed * Zac Hatfield-Dodds - https://zhd.dev * Mateus Berardo de Souza Terra - https://github.com/MatTerra * Ganden Schaffner - https://github.com/gschaffner * Michał Górny - https://github.com/mgorny * Bernát Gábor - https://github.com/gaborbernat * Pamela McA'Nulty - https://github.com/PamelaM * Christian Riedel - https://github.com/Cielquan * Chris Sreesangkom - https://github.com/csreesan * Sorin Sbarnea - https://github.com/ssbarnea * Brian Rutledge - https://github.com/bhrutledge * Danilo Šegan - https://github.com/dsegan * Michał Bielawski - https://github.com/D3X * Zac Hatfield-Dodds - https://github.com/Zac-HD * Ben Greiner - https://github.com/bnavigator * Delgan - https://github.com/Delgan * Andre Brisco - https://github.com/abrisco * Colin O'Dell - https://github.com/colinodell * Ronny Pfannschmidt - https://github.com/RonnyPfannschmidt * Christian Fetzer - https://github.com/fetzerch * Jonathan Stewmon - https://github.com/jstewmon * Matthew Gamble - https://github.com/mwgamble python-pytest-cov-4.1.0/CHANGELOG.rst000066400000000000000000000455271444602742700172200ustar00rootroot00000000000000Changelog ========= 4.1.0 (2023-05-24) ------------------ * Updated CI with new Pythons and dependencies. * Removed rsyncdir support. This makes pytest-cov compatible with xdist 3.0. Contributed by Sorin Sbarnea in `#558 `_. * Optimized summary generation to not be performed if no reporting is active (for example, when ``--cov-report=''`` is used without ``--cov-fail-under``). Contributed by Jonathan Stewmon in `#589 `_. * Added support for JSON reporting. Contributed by Matthew Gamble in `#582 `_. * Refactored code to use f-strings. Contributed by Mark Mayo in `#572 `_. * Fixed a skip in the test suite for some old xdist. Contributed by a bunch of people in `#565 `_. 4.0.0 (2022-09-28) ------------------ **Note that this release drops support for multiprocessing.** * `--cov-fail-under` no longer causes `pytest --collect-only` to fail Contributed by Zac Hatfield-Dodds in `#511 `_. * Dropped support for multiprocessing (mostly because `issue 82408 `_). This feature was mostly working but very broken in certain scenarios and made the test suite very flaky and slow. There is builtin multiprocessing support in coverage and you can migrate to that. All you need is this in your ``.coveragerc``:: [run] concurrency = multiprocessing parallel = true sigterm = true * Fixed deprecation in ``setup.py`` by trying to import setuptools before distutils. Contributed by Ben Greiner in `#545 `_. * Removed undesirable new lines that were displayed while reporting was disabled. Contributed by Delgan in `#540 `_. * Documentation fixes. Contributed by Andre Brisco in `#543 `_ and Colin O'Dell in `#525 `_. * Added support for LCOV output format via `--cov-report=lcov`. Only works with coverage 6.3+. Contributed by Christian Fetzer in `#536 `_. * Modernized pytest hook implementation. Contributed by Bruno Oliveira in `#549 `_ and Ronny Pfannschmidt in `#550 `_. 3.0.0 (2021-10-04) ------------------- **Note that this release drops support for Python 2.7 and Python 3.5.** * Added support for Python 3.10 and updated various test dependencies. Contributed by Hugo van Kemenade in `#500 `_. * Switched from Travis CI to GitHub Actions. Contributed by Hugo van Kemenade in `#494 `_ and `#495 `_. * Add a ``--cov-reset`` CLI option. Contributed by Danilo Šegan in `#459 `_. * Improved validation of ``--cov-fail-under`` CLI option. Contributed by ... Ronny Pfannschmidt's desire for skark in `#480 `_. * Dropped Python 2.7 support. Contributed by Thomas Grainger in `#488 `_. * Updated trove classifiers. Contributed by Michał Bielawski in `#481 `_. * Reverted change for `toml` requirement. Contributed by Thomas Grainger in `#477 `_. 2.12.1 (2021-06-01) ------------------- * Changed the `toml` requirement to be always be directly required (instead of being required through a coverage extra). This fixes issues with pip-compile (`pip-tools#1300 `_). Contributed by Sorin Sbarnea in `#472 `_. * Documented ``show_contexts``. Contributed by Brian Rutledge in `#473 `_. 2.12.0 (2021-05-14) ------------------- * Added coverage's `toml` extra to install requirements in setup.py. Contributed by Christian Riedel in `#410 `_. * Fixed ``pytest_cov.__version__`` to have the right value (string with version instead of a string including ``__version__ =``). * Fixed license classifier in ``setup.py``. Contributed by Chris Sreesangkom in `#467 `_. * Fixed *commits since* badge. Contributed by Terence Honles in `#470 `_. 2.11.1 (2021-01-20) ------------------- * Fixed support for newer setuptools (v42+). Contributed by Michał Górny in `#451 `_. 2.11.0 (2021-01-18) ------------------- * Bumped minimum coverage requirement to 5.2.1. This prevents reporting issues. Contributed by Mateus Berardo de Souza Terra in `#433 `_. * Improved sample projects (from the `examples `_ directory) to support running `tox -e pyXY`. Now the example configures a suffixed coverage data file, and that makes the cleanup environment unnecessary. Contributed by Ganden Schaffner in `#435 `_. * Removed the empty `console_scripts` entrypoint that confused some Gentoo build script. I didn't ask why it was so broken cause I didn't want to ruin my day. Contributed by Michał Górny in `#434 `_. * Fixed the missing `coverage context `_ when using subprocesses. Contributed by Bernát Gábor in `#443 `_. * Updated the config section in the docs. Contributed by Pamela McA'Nulty in `#429 `_. * Migrated CI to travis-ci.com (from .org). 2.10.1 (2020-08-14) ------------------- * Support for ``pytest-xdist`` 2.0, which breaks compatibility with ``pytest-xdist`` before 1.22.3 (from 2017). Contributed by Zac Hatfield-Dodds in `#412 `_. * Fixed the ``LocalPath has no attribute startswith`` failure that occurred when using the ``pytester`` plugin in inline mode. 2.10.0 (2020-06-12) ------------------- * Improved the ``--no-cov`` warning. Now it's only shown if ``--no-cov`` is present before ``--cov``. * Removed legacy pytest support. Changed ``setup.py`` so that ``pytest>=4.6`` is required. 2.9.0 (2020-05-22) ------------------ * Fixed ``RemovedInPytest4Warning`` when using Pytest 3.10. Contributed by Michael Manganiello in `#354 `_. * Made pytest startup faster when plugin not active by lazy-importing. Contributed by Anders Hovmöller in `#339 `_. * Various CI improvements. Contributed by Daniel Hahler in `#363 `_ and `#364 `_. * Various Python support updates (drop EOL 3.4, test against 3.8 final). Contributed by Hugo van Kemenade in `#336 `_ and `#367 `_. * Changed ``--cov-append`` to always enable ``data_suffix`` (a coverage setting). Contributed by Harm Geerts in `#387 `_. * Changed ``--cov-append`` to handle loading previous data better (fixes various path aliasing issues). * Various other testing improvements, github issue templates, example updates. * Fixed internal failures that are caused by tests that change the current working directory by ensuring a consistent working directory when coverage is called. See `#306 `_ and `coveragepy#881 `_ 2.8.1 (2019-10-05) ------------------ * Fixed `#348 `_ - regression when only certain reports (html or xml) are used then ``--cov-fail-under`` always fails. 2.8.0 (2019-10-04) ------------------ * Fixed ``RecursionError`` that can occur when using `cleanup_on_signal `__ or `cleanup_on_sigterm `__. See: `#294 `_. The 2.7.x releases of pytest-cov should be considered broken regarding aforementioned cleanup API. * Added compatibility with future xdist release that deprecates some internals (match pytest-xdist master/worker terminology). Contributed by Thomas Grainger in `#321 `_ * Fixed breakage that occurs when multiple reporting options are used. Contributed by Thomas Grainger in `#338 `_. * Changed internals to use a stub instead of ``os.devnull``. Contributed by Thomas Grainger in `#332 `_. * Added support for Coverage 5.0. Contributed by Ned Batchelder in `#319 `_. * Added support for float values in ``--cov-fail-under``. Contributed by Martín Gaitán in `#311 `_. * Various documentation fixes. Contributed by Juanjo Bazán, Andrew Murray and Albert Tugushev in `#298 `_, `#299 `_ and `#307 `_. * Various testing improvements. Contributed by Ned Batchelder, Daniel Hahler, Ionel Cristian Mărieș and Hugo van Kemenade in `#313 `_, `#314 `_, `#315 `_, `#316 `_, `#325 `_, `#326 `_, `#334 `_ and `#335 `_. * Added the ``--cov-context`` CLI options that enables coverage contexts. Only works with coverage 5.0+. Contributed by Ned Batchelder in `#345 `_. 2.7.1 (2019-05-03) ------------------ * Fixed source distribution manifest so that garbage ain't included in the tarball. 2.7.0 (2019-05-03) ------------------ * Fixed ``AttributeError: 'NoneType' object has no attribute 'configure_node'`` error when ``--no-cov`` is used. Contributed by Alexander Shadchin in `#263 `_. * Various testing and CI improvements. Contributed by Daniel Hahler in `#255 `_, `#266 `_, `#272 `_, `#271 `_ and `#269 `_. * Improved ``pytest_cov.embed.cleanup_on_sigterm`` to be reentrant (signal deliveries while signal handling is running won't break stuff). * Added ``pytest_cov.embed.cleanup_on_signal`` for customized cleanup. * Improved cleanup code and fixed various issues with leftover data files. All contributed in `#265 `_ or `#262 `_. * Improved examples. Now there are two examples for the common project layouts, complete with working coverage configuration. The examples have CI testing. Contributed in `#267 `_. * Improved help text for CLI options. 2.6.1 (2019-01-07) ------------------ * Added support for Pytest 4.1. Contributed by Daniel Hahler and Семён Марьясин in `#253 `_ and `#230 `_. * Various test and docs fixes. Contributed by Daniel Hahler in `#224 `_ and `#223 `_. * Fixed the "Module already imported" issue (`#211 `_). Contributed by Daniel Hahler in `#228 `_. 2.6.0 (2018-09-03) ------------------ * Dropped support for Python 3 < 3.4, Pytest < 3.5 and Coverage < 4.4. * Fixed some documentation formatting. Contributed by Jean Jordaan and Julian. * Added an example with ``addopts`` in documentation. Contributed by Samuel Giffard in `#195 `_. * Fixed ``TypeError: 'NoneType' object is not iterable`` in certain xdist configurations. Contributed by Jeremy Bowman in `#213 `_. * Added a ``no_cover`` marker and fixture. Fixes `#78 `_. * Fixed broken ``no_cover`` check when running doctests. Contributed by Terence Honles in `#200 `_. * Fixed various issues with path normalization in reports (when combining coverage data from parallel mode). Fixes `#130 `_. Contributed by Ryan Hiebert & Ionel Cristian Mărieș in `#178 `_. * Report generation failures don't raise exceptions anymore. A warning will be logged instead. Fixes `#161 `_. * Fixed multiprocessing issue on Windows (empty env vars are not passed). Fixes `#165 `_. 2.5.1 (2017-05-11) ------------------ * Fixed xdist breakage (regression in ``2.5.0``). Fixes `#157 `_. * Allow setting custom ``data_file`` name in ``.coveragerc``. Fixes `#145 `_. Contributed by Jannis Leidel & Ionel Cristian Mărieș in `#156 `_. 2.5.0 (2017-05-09) ------------------ * Always show a summary when ``--cov-fail-under`` is used. Contributed by Francis Niu in `PR#141 `_. * Added ``--cov-branch`` option. Fixes `#85 `_. * Improve exception handling in subprocess setup. Fixes `#144 `_. * Fixed handling when ``--cov`` is used multiple times. Fixes `#151 `_. 2.4.0 (2016-10-10) ------------------ * Added a "disarm" option: ``--no-cov``. It will disable coverage measurements. Contributed by Zoltan Kozma in `PR#135 `_. **WARNING: Do not put this in your configuration files, it's meant to be an one-off for situations where you want to disable coverage from command line.** * Fixed broken exception handling on ``.pth`` file. See `#136 `_. 2.3.1 (2016-08-07) ------------------ * Fixed regression causing spurious errors when xdist was used. See `#124 `_. * Fixed DeprecationWarning about incorrect `addoption` use. Contributed by Florian Bruhin in `PR#127 `_. * Fixed deprecated use of funcarg fixture API. Contributed by Daniel Hahler in `PR#125 `_. 2.3.0 (2016-07-05) ------------------ * Add support for specifying output location for html, xml, and annotate report. Contributed by Patrick Lannigan in `PR#113 `_. * Fix bug hiding test failure when cov-fail-under failed. * For coverage >= 4.0, match the default behaviour of `coverage report` and error if coverage fails to find the source instead of just printing a warning. Contributed by David Szotten in `PR#116 `_. * Fixed bug occurred when bare ``--cov`` parameter was used with xdist. Contributed by Michael Elovskikh in `PR#120 `_. * Add support for ``skip_covered`` and added ``--cov-report=term-skip-covered`` command line options. Contributed by Saurabh Kumar in `PR#115 `_. 2.2.1 (2016-01-30) ------------------ * Fixed incorrect merging of coverage data when xdist was used and coverage was ``>= 4.0``. 2.2.0 (2015-10-04) ------------------ * Added support for changing working directory in tests. Previously changing working directory would disable coverage measurements in suprocesses. * Fixed broken handling for ``--cov-report=annotate``. 2.1.0 (2015-08-23) ------------------ * Added support for `coverage 4.0b2`. * Added the ``--cov-append`` command line options. Contributed by Christian Ledermann in `PR#80 `_. 2.0.0 (2015-07-28) ------------------ * Added ``--cov-fail-under``, akin to the new ``fail_under`` option in `coverage-4.0` (automatically activated if there's a ``[report] fail_under = ...`` in ``.coveragerc``). * Changed ``--cov-report=term`` to automatically upgrade to ``--cov-report=term-missing`` if there's ``[run] show_missing = True`` in ``.coveragerc``. * Changed ``--cov`` so it can be used with no path argument (in which case the source settings from ``.coveragerc`` will be used instead). * Fixed `.pth` installation to work in all cases (install, easy_install, wheels, develop etc). * Fixed `.pth` uninstallation to work for wheel installs. * Support for coverage 4.0. * Data file suffixing changed to use coverage's ``data_suffix=True`` option (instead of the custom suffixing). * Avoid warning about missing coverage data (just like ``coverage.control.process_startup``). * Fixed a race condition when running with xdist (all the workers tried to combine the files). It's possible that this issue is not present in `pytest-cov 1.8.X`. 1.8.2 (2014-11-06) ------------------ * N/A python-pytest-cov-4.1.0/CONTRIBUTING.rst000066400000000000000000000051771444602742700176350ustar00rootroot00000000000000============ 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:YOURGITHUBNAME/pytest-cov.git 3. Create a branch for local development:: git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 4. When you're done making changes run all the checks and docs builder with `tox `_ 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 GitHub Actions - it will `run the tests `_ for each change you add in the pull request. It will be slower though ... Tips ---- To run a subset of tests:: tox -e envname -- pytest -k test_myfeature To run all the test environments in *parallel*:: tox -p auto python-pytest-cov-4.1.0/LICENSE000066400000000000000000000020571444602742700161730ustar00rootroot00000000000000The 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. python-pytest-cov-4.1.0/MANIFEST.in000066400000000000000000000010551444602742700167210ustar00rootroot00000000000000graft docs graft examples prune examples/*/.tox prune examples/*/htmlcov prune examples/*/*/htmlcov prune examples/adhoc-layout/*.egg-info prune examples/src-layout/src/*.egg-info graft .github/workflows graft src graft ci graft tests include .bumpversion.cfg include .cookiecutterrc include .coveragerc include .editorconfig include tox.ini include .readthedocs.yml include .pre-commit-config.yaml include AUTHORS.rst include CHANGELOG.rst include CONTRIBUTING.rst include LICENSE include README.rst global-exclude *.py[cod] __pycache__/* *.so *.dylib python-pytest-cov-4.1.0/README.rst000066400000000000000000000126441444602742700166600ustar00rootroot00000000000000======== Overview ======== .. start-badges .. list-table:: :stub-columns: 1 * - docs - |docs| * - tests - | |github-actions| |requires| | * - package - | |version| |conda-forge| |wheel| |supported-versions| |supported-implementations| | |commits-since| .. |docs| image:: https://readthedocs.org/projects/pytest-cov/badge/?style=flat :target: https://readthedocs.org/projects/pytest-cov :alt: Documentation Status .. |github-actions| image:: https://github.com/pytest-dev/pytest-cov/actions/workflows/test.yml/badge.svg :alt: GitHub Actions Status :target: https://github.com/pytest-dev/pytest-cov/actions .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/pytest-dev/pytest-cov?branch=master&svg=true :alt: AppVeyor Build Status :target: https://ci.appveyor.com/project/pytestbot/pytest-cov .. |requires| image:: https://requires.io/github/pytest-dev/pytest-cov/requirements.svg?branch=master :alt: Requirements Status :target: https://requires.io/github/pytest-dev/pytest-cov/requirements/?branch=master .. |version| image:: https://img.shields.io/pypi/v/pytest-cov.svg :alt: PyPI Package latest release :target: https://pypi.org/project/pytest-cov .. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pytest-cov.svg :target: https://anaconda.org/conda-forge/pytest-cov .. |commits-since| image:: https://img.shields.io/github/commits-since/pytest-dev/pytest-cov/v4.1.0.svg :alt: Commits since latest release :target: https://github.com/pytest-dev/pytest-cov/compare/v4.1.0...master .. |wheel| image:: https://img.shields.io/pypi/wheel/pytest-cov.svg :alt: PyPI Wheel :target: https://pypi.org/project/pytest-cov .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/pytest-cov.svg :alt: Supported versions :target: https://pypi.org/project/pytest-cov .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/pytest-cov.svg :alt: Supported implementations :target: https://pypi.org/project/pytest-cov .. end-badges This plugin produces coverage reports. Compared to just using ``coverage run`` this plugin does some extras: * Subprocess support: you can fork or run stuff in a subprocess and will get covered without any fuss. * Xdist support: you can use all of pytest-xdist's features and still get coverage. * Consistent pytest behavior. If you run ``coverage run -m pytest`` you will have slightly different ``sys.path`` (CWD will be in it, unlike when running ``pytest``). All features offered by the coverage package should work, either through pytest-cov's command line options or through coverage's config file. * Free software: MIT license Installation ============ Install with pip:: pip install pytest-cov For distributed testing support install pytest-xdist:: pip install pytest-xdist Upgrading from ancient pytest-cov --------------------------------- `pytest-cov 2.0` is using a new ``.pth`` file (``pytest-cov.pth``). You may want to manually remove the older ``init_cov_core.pth`` from site-packages as it's not automatically removed. Uninstalling ------------ Uninstall with pip:: pip uninstall pytest-cov Under certain scenarios a stray ``.pth`` file may be left around in site-packages. * `pytest-cov 2.0` may leave a ``pytest-cov.pth`` if you installed without wheels (``easy_install``, ``setup.py install`` etc). * `pytest-cov 1.8 or older` will leave a ``init_cov_core.pth``. Usage ===== :: pytest --cov=myproj tests/ Would produce a report like:: -------------------- coverage: ... --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94% Documentation ============= http://pytest-cov.rtfd.org/ Coverage Data File ================== The data file is erased at the beginning of testing to ensure clean data for each test run. If you need to combine the coverage of several test runs you can use the ``--cov-append`` option to append this coverage data to coverage data from previous test runs. The data file is left at the end of testing so that it is possible to use normal coverage tools to examine it. Limitations =========== For distributed testing the workers must have the pytest-cov package installed. This is needed since the plugin must be registered through setuptools for pytest to start the plugin on the worker. For subprocess measurement environment variables must make it from the main process to the subprocess. The python used by the subprocess must have pytest-cov installed. The subprocess must do normal site initialisation so that the environment variables can be detected and coverage started. Acknowledgements ================ Whilst this plugin has been built fresh from the ground up it has been influenced by the work done on pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and nose-cover (Jason Pellerin) which are other coverage plugins. Ned Batchelder for coverage and its ability to combine the coverage results of parallel runs. Holger Krekel for pytest with its distributed testing support. Jason Pellerin for nose. Michael Foord for unittest2. No doubt others have contributed to these tools as well. python-pytest-cov-4.1.0/ci/000077500000000000000000000000001444602742700155555ustar00rootroot00000000000000python-pytest-cov-4.1.0/ci/bootstrap.py000077500000000000000000000054571444602742700201620ustar00rootroot00000000000000#!/usr/bin/env python import os import subprocess import sys from os.path import abspath from os.path import dirname from os.path import exists from os.path import join from os.path import relpath base_path = dirname(dirname(abspath(__file__))) templates_path = join(base_path, "ci", "templates") def check_call(args): print("+", *args) subprocess.check_call(args) def exec_in_env(): env_path = join(base_path, ".tox", "bootstrap") if sys.platform == "win32": bin_path = join(env_path, "Scripts") else: bin_path = join(env_path, "bin") if not exists(env_path): import subprocess print(f"Making bootstrap env in: {env_path} ...") try: check_call([sys.executable, "-m", "venv", env_path]) except subprocess.CalledProcessError: try: check_call([sys.executable, "-m", "virtualenv", env_path]) except subprocess.CalledProcessError: check_call(["virtualenv", env_path]) print("Installing `jinja2` into bootstrap environment...") check_call([join(bin_path, "pip"), "install", "jinja2", "tox"]) python_executable = join(bin_path, "python") if not os.path.exists(python_executable): python_executable += '.exe' print(f"Re-executing with: {python_executable}") print("+ exec", python_executable, __file__, "--no-env") os.execv(python_executable, [python_executable, __file__, "--no-env"]) def main(): import jinja2 print(f"Project path: {base_path}") jinja = jinja2.Environment( loader=jinja2.FileSystemLoader(templates_path), trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=True ) tox_environments = [ line.strip() # 'tox' need not be installed globally, but must be importable # by the Python that is running this script. # This uses sys.executable the same way that the call in # cookiecutter-pylibrary/hooks/post_gen_project.py # invokes this bootstrap.py itself. for line in subprocess.check_output([sys.executable, '-m', 'tox', '--listenvs'], text=True).splitlines() ] tox_environments = [line for line in tox_environments if line.startswith('py')] for root, _, files in os.walk(templates_path): for name in files: relative = relpath(root, templates_path) with open(join(base_path, relative, name), "w") as fh: fh.write(jinja.get_template(join(relative, name)).render(tox_environments=tox_environments)) print(f"Wrote {name}") print("DONE.") if __name__ == "__main__": args = sys.argv[1:] if args == ["--no-env"]: main() elif not args: exec_in_env() else: print(f"Unexpected arguments {args}", file=sys.stderr) sys.exit(1) python-pytest-cov-4.1.0/ci/requirements.txt000066400000000000000000000001021444602742700210320ustar00rootroot00000000000000virtualenv>=16.6.0 pip>=19.1.1 setuptools>=18.0.1 six>=1.14.0 tox python-pytest-cov-4.1.0/ci/templates/000077500000000000000000000000001444602742700175535ustar00rootroot00000000000000python-pytest-cov-4.1.0/ci/templates/.github/000077500000000000000000000000001444602742700211135ustar00rootroot00000000000000python-pytest-cov-4.1.0/ci/templates/.github/workflows/000077500000000000000000000000001444602742700231505ustar00rootroot00000000000000python-pytest-cov-4.1.0/ci/templates/.github/workflows/test.yml000066400000000000000000000071151444602742700246560ustar00rootroot00000000000000name: Tests on: [push, pull_request, workflow_dispatch] jobs: {%- raw %} examples: runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["pypy-3.9", "3.11"] target: [ "src-layout", "adhoc-layout", ] include: # Add new helper variables to existing jobs - {python-version: "pypy-3.9", tox-python-version: "pypy3"} - {python-version: "3.11", tox-python-version: "py311"} steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Cache uses: actions/cache@v3 with: path: ~/.cache/pip key: examples-v1-${{ hashFiles('**/tox.ini') }} restore-keys: | examples-v1- - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install --upgrade wheel python -m pip install --progress-bar=off tox -rci/requirements.txt - name: Examples run: | cd examples/${{ matrix.target }} tox -v -e ${{ matrix.tox-python-version }} {%- endraw %} test: name: {{ '${{ matrix.name }}' }} runs-on: {{ '${{ matrix.os }}' }} timeout-minutes: 30 strategy: fail-fast: false matrix: include: - name: 'check' python: '3.11' toxpython: 'python3.11' tox_env: 'check' os: 'ubuntu-latest' - name: 'docs' python: '3.11' toxpython: 'python3.11' tox_env: 'docs' os: 'ubuntu-latest' {% for env in tox_environments %} {% set prefix = env.split('-')[0] -%} {% if prefix.startswith('pypy') %} {% set python %}pypy-{{ prefix[4] }}.{{ prefix[5] }}{% endset %} {% set cpython %}pp{{ prefix[4:5] }}{% endset %} {% set toxpython %}pypy{{ prefix[4] }}.{{ prefix[5] }}{% endset %} {% else %} {% set python %}{{ prefix[2] }}.{{ prefix[3:] }}{% endset %} {% set cpython %}cp{{ prefix[2:] }}{% endset %} {% set toxpython %}python{{ prefix[2] }}.{{ prefix[3:] }}{% endset %} {% endif %} {% for os, python_arch in [ ['ubuntu', 'x64'], ['windows', 'x64'], ['macos', 'x64'], ] %} - name: '{{ env }} ({{ os }})' python: '{{ python }}' toxpython: '{{ toxpython }}' python_arch: '{{ python_arch }}' tox_env: '{{ env }}' os: '{{ os }}-latest' {% endfor %} {% endfor %} steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-python@v4 with: python-version: {{ '${{ matrix.python }}' }} architecture: {{ '${{ matrix.python_arch }}' }} - name: install dependencies run: | python -mpip install --progress-bar=off -r ci/requirements.txt virtualenv --version pip --version tox --version pip list --format=freeze - name: test env: TOXPYTHON: '{{ '${{ matrix.toxpython }}' }}' run: > tox -e {{ '${{ matrix.tox_env }}' }} -v {% raw %} successful: # this provides a single status check for branch merge rules # (use this in `Require status checks to pass before merging` in branch settings) if: always() needs: - test - examples runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} {% endraw %} python-pytest-cov-4.1.0/docs/000077500000000000000000000000001444602742700161125ustar00rootroot00000000000000python-pytest-cov-4.1.0/docs/authors.rst000066400000000000000000000000341444602742700203260ustar00rootroot00000000000000.. include:: ../AUTHORS.rst python-pytest-cov-4.1.0/docs/changelog.rst000066400000000000000000000000361444602742700205720ustar00rootroot00000000000000.. include:: ../CHANGELOG.rst python-pytest-cov-4.1.0/docs/conf.py000066400000000000000000000024251444602742700174140ustar00rootroot00000000000000import os import sphinx_py3doc_enhanced_theme extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', 'sphinx.ext.extlinks', ] if os.getenv('SPELLCHECK'): extensions += 'sphinxcontrib.spelling', spelling_show_suggestions = True spelling_lang = 'en_US' source_suffix = '.rst' master_doc = 'index' project = 'pytest-cov' year = '2016' author = 'pytest-cov contributors' copyright = f'{year}, {author}' version = release = '4.1.0' pygments_style = 'trac' templates_path = ['.'] extlinks = { 'issue': ('https://github.com/pytest-dev/pytest-cov/issues/%s', '#'), 'pr': ('https://github.com/pytest-dev/pytest-cov/pull/%s', 'PR #'), } html_theme = "sphinx_py3doc_enhanced_theme" html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()] html_theme_options = { 'githuburl': 'https://github.com/pytest-dev/pytest-cov/' } html_use_smartypants = True html_last_updated_fmt = '%b %d, %Y' html_split_index = True html_sidebars = { '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], } html_short_title = f'{project}-{version}' napoleon_use_ivar = True napoleon_use_rtype = False napoleon_use_param = False python-pytest-cov-4.1.0/docs/config.rst000066400000000000000000000065001444602742700201120ustar00rootroot00000000000000============= Configuration ============= This plugin provides a clean minimal set of command line options that are added to pytest. For further control of coverage use a coverage config file. For example if tests are contained within the directory tree being measured the tests may be excluded if desired by using a .coveragerc file with the omit option set:: pytest --cov-config=.coveragerc --cov=myproj myproj/tests/ Where the .coveragerc file contains file globs:: [run] omit = tests/* For full details refer to the `coverage config file`_ documentation. .. _`coverage config file`: https://coverage.readthedocs.io/en/latest/config.html .. note:: Important Note This plugin overrides the ``parallel`` option of coverage. Unless you also run coverage without pytest-cov it's pointless to set those options in your ``.coveragerc``. If you use the ``--cov=something`` option (with a value) then coverage's ``source`` option will also get overridden. If you have multiple sources it might be easier to set those in ``.coveragerc`` and always use ``--cov`` (without a value) instead of having a long command line with ``--cov=pkg1 --cov=pkg2 --cov=pkg3 ...``. If you use the ``--cov-branch`` option then coverage's ``branch`` option will also get overridden. If you wish to always add pytest-cov with pytest, you can use ``addopts`` under ``pytest`` or ``tool:pytest`` section. For example: :: [tool:pytest] addopts = --cov= --cov-report html Caveats ======= A unfortunate consequence of coverage.py's history is that ``.coveragerc`` is a magic name: it's the default file but it also means "try to also lookup coverage configuration in ``tox.ini`` or ``setup.cfg``". In practical terms this means that if you have your coverage configuration in ``tox.ini`` or ``setup.cfg`` it is paramount that you also use ``--cov-config=tox.ini`` or ``--cov-config=setup.cfg``. You might not be affected but it's unlikely that you won't ever use ``chdir`` in a test. Reference ========= The complete list of command line options is: --cov=PATH Measure coverage for filesystem path. (multi-allowed) --cov-report=type Type of report to generate: term, term-missing, annotate, html, xml, json, lcov (multi-allowed). term, term- missing may be followed by ":skip-covered". annotate, html, xml, json and lcov may be followed by ":DEST" where DEST specifies the output location. Use --cov-report= to not generate any output. --cov-config=path Config file for coverage. Default: .coveragerc --no-cov-on-fail Do not report coverage if test run fails. Default: False --no-cov Disable coverage report completely (useful for debuggers). Default: False --cov-reset Reset cov sources accumulated in options so far. Mostly useful for scripts and configuration files. --cov-fail-under=MIN Fail if the total coverage is less than MIN. --cov-append Do not delete coverage but append to current. Default: False --cov-branch Enable branch coverage. --cov-context Choose the method for setting the dynamic context. python-pytest-cov-4.1.0/docs/contexts.rst000066400000000000000000000022131444602742700205110ustar00rootroot00000000000000======== Contexts ======== Coverage.py 5.0 can record separate coverage data for `different contexts`_ during one run of a test suite. Pytest-cov can use this feature to record coverage data for each test individually, with the ``--cov-context=test`` option. .. _different contexts: https://coverage.readthedocs.io/en/stable/contexts.html The context name recorded in the coverage.py database is the pytest test id, and the phase of execution, one of "setup", "run", or "teardown". These two are separated with a pipe symbol. You might see contexts like:: test_functions.py::test_addition|run test_fancy.py::test_parametrized[1-101]|setup test_oldschool.py::RegressionTests::test_error|run Note that parameterized tests include the values of the parameters in the test id, and each set of parameter values is recorded as a separate test. To view contexts when using ``--cov-report=html``, add this to your ``.coveragerc``:: [html] show_contexts = True The HTML report will include an annotation on each covered line, indicating the number of contexts that executed the line. Clicking the annotation displays a list of the contexts. python-pytest-cov-4.1.0/docs/contributing.rst000066400000000000000000000000411444602742700213460ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst python-pytest-cov-4.1.0/docs/debuggers.rst000066400000000000000000000017051444602742700206160ustar00rootroot00000000000000===================== Debuggers and PyCharm ===================== (or other IDEs) When it comes to TDD one obviously would like to debug tests. Debuggers in Python use mostly the sys.settrace function to gain access to context. Coverage uses the same technique to get access to the lines executed. Coverage does not play well with other tracers simultaneously running. This manifests itself in behaviour that PyCharm might not hit a breakpoint no matter what the user does, or encountering an error like this:: PYDEV DEBUGGER WARNING: sys.settrace() should not be used when the debugger is being used. This may cause the debugger to stop working correctly. 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! python-pytest-cov-4.1.0/docs/index.rst000066400000000000000000000006271444602742700177600ustar00rootroot00000000000000Welcome to pytest-cov's documentation! ====================================== Contents: .. toctree:: :maxdepth: 2 readme config reporting debuggers xdist subprocess-support contexts tox plugins markers-fixtures changelog authors releasing contributing Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-pytest-cov-4.1.0/docs/markers-fixtures.rst000066400000000000000000000014441444602742700221620ustar00rootroot00000000000000==================== Markers and fixtures ==================== There are some builtin markers and fixtures in ``pytest-cov``. Markers ======= ``no_cover`` ------------ Eg: .. code-block:: python @pytest.mark.no_cover def test_foobar(): # do some stuff that needs coverage disabled .. warning:: Caveat Note that subprocess coverage will also be disabled. Fixtures ======== ``no_cover`` ------------ Eg: .. code-block:: python def test_foobar(no_cover): # same as the marker ... ``cov`` ------- For reasons that no one can remember there is a ``cov`` fixture that provides access to the underlying Coverage instance. Some say this is a disguised foot-gun and should be removed, and some think mysteries make life more interesting and it should be left alone. python-pytest-cov-4.1.0/docs/plugins.rst000066400000000000000000000017441444602742700203330ustar00rootroot00000000000000=============== Plugin coverage =============== Getting coverage on pytest plugins is a very particular situation. Because how pytest implements plugins (using setuptools entrypoints) it doesn't allow controlling the order in which the plugins load. See `pytest/issues/935 `_ for technical details. The current way of dealing with this problem is using the append feature and manually starting ``pytest-cov``'s engine, eg:: COV_CORE_SOURCE=src COV_CORE_CONFIG=.coveragerc COV_CORE_DATAFILE=.coverage.eager pytest --cov=src --cov-append Alternatively you can have this in ``tox.ini`` (if you're using `Tox `_ of course):: [testenv] setenv = COV_CORE_SOURCE= COV_CORE_CONFIG={toxinidir}/.coveragerc COV_CORE_DATAFILE={toxinidir}/.coverage And in ``pytest.ini`` / ``tox.ini`` / ``setup.cfg``:: [tool:pytest] addopts = --cov --cov-append python-pytest-cov-4.1.0/docs/readme.rst000066400000000000000000000000331444602742700200750ustar00rootroot00000000000000.. include:: ../README.rst python-pytest-cov-4.1.0/docs/releasing.rst000066400000000000000000000025631444602742700206230ustar00rootroot00000000000000========= 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 `GitHub Actions `_ to give the green builds. #. Check that the docs on `ReadTheDocs `_ are built. #. Make sure you have a clean checkout, run ``git status`` to verify. #. Manually clean temporary files (that are ignored and won't show up in ``git status``):: rm -rf dist build src/*.egg-info These files need to be removed to force distutils/setuptools to rebuild everything and recreate the egg-info metadata. #. Build the dists:: python3 setup.py clean --all sdist bdist_wheel #. Verify that the resulting archives (found in ``dist/``) are good. #. Upload the sdist and wheel with twine:: twine upload dist/* python-pytest-cov-4.1.0/docs/reporting.rst000066400000000000000000000060141444602742700206560ustar00rootroot00000000000000Reporting ========= 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, JSON, LCOV and annotated source code. The terminal report without line numbers (default):: pytest --cov-report term --cov=myproj tests/ -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94% The terminal report with line numbers:: pytest --cov-report term-missing --cov=myproj tests/ -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover Missing -------------------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% 24-26, 99, 149, 233-236, 297-298, 369-370 myproj/feature4286 94 7 92% 183-188, 197 -------------------------------------------------- TOTAL 353 20 94% The terminal report with skip covered:: pytest --cov-report term:skip-covered --cov=myproj tests/ -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94% 1 files skipped due to complete coverage. You can use ``skip-covered`` with ``term-missing`` as well. e.g. ``--cov-report term-missing:skip-covered`` These four report options output to files without showing anything on the terminal:: pytest --cov-report html --cov-report xml --cov-report json --cov-report lcov --cov-report annotate --cov=myproj tests/ The output location for each of these reports can be specified. The output location for the XML, JSON and LCOV report is a file. Where as the output location for the HTML and annotated source code reports are directories:: pytest --cov-report html:cov_html --cov-report xml:cov.xml --cov-report json:cov.json --cov-report lcov:cov.info --cov-report annotate:cov_annotate --cov=myproj tests/ The final report option can also suppress printing to the terminal:: pytest --cov-report= --cov=myproj tests/ This mode can be especially useful on continuous integration servers, where a coverage file is needed for subsequent processing, but no local report needs to be viewed. For example, tests run on GitHub Actions could produce a .coverage file for use with Coveralls. python-pytest-cov-4.1.0/docs/requirements.txt000066400000000000000000000001211444602742700213700ustar00rootroot00000000000000sphinx==3.0.3 sphinx-py3doc-enhanced-theme==2.4.0 docutils==0.16 jinja2<3.1 -e . python-pytest-cov-4.1.0/docs/spelling_wordlist.txt000066400000000000000000000001551444602742700224200ustar00rootroot00000000000000builtin builtins classmethod staticmethod classmethods staticmethods args kwargs callstack Changelog Indices python-pytest-cov-4.1.0/docs/subprocess-support.rst000066400000000000000000000125751444602742700225600ustar00rootroot00000000000000================== Subprocess support ================== Normally coverage writes the data via a pretty standard atexit handler. However, if the subprocess doesn't exit on its own then the atexit handler might not run. Why that happens is best left to the adventurous to discover by waddling through the Python bug tracker. pytest-cov supports subprocesses and multiprocessing, and works around these atexit limitations. However, there are a few pitfalls that need to be explained. If you use ``multiprocessing.Pool`` =================================== **pytest-cov** automatically registers a multiprocessing finalizer. The finalizer will only run reliably if the pool is closed. Closing the pool basically signals the workers that there will be no more work, and they will eventually exit. Thus one also needs to call `join` on the pool. If you use ``multiprocessing.Pool.terminate`` or the context manager API (``__exit__`` will just call ``terminate``) then the workers can get SIGTERM and then the finalizers won't run or complete in time. Thus you need to make sure your ``multiprocessing.Pool`` gets a nice and clean exit: .. code-block:: python from multiprocessing import Pool def f(x): return x*x if __name__ == '__main__': p = Pool(5) try: print(p.map(f, [1, 2, 3])) finally: p.close() # Marks the pool as closed. p.join() # Waits for workers to exit. If you must use the context manager API (e.g.: the pool is managed in third party code you can't change) then you can register a cleaning SIGTERM handler like so: .. warning:: **This technique cannot be used on Python 3.8** (registering signal handlers will cause deadlocks in the pool, see: https://bugs.python.org/issue38227). .. code-block:: python from multiprocessing import Pool def f(x): return x*x if __name__ == '__main__': try: from pytest_cov.embed import cleanup_on_sigterm except ImportError: pass else: cleanup_on_sigterm() with Pool(5) as p: print(p.map(f, [1, 2, 3])) If you use ``multiprocessing.Process`` ====================================== There's similar issue when using the ``Process`` objects. Don't forget to use ``.join()``: .. code-block:: python from multiprocessing import Process def f(name): print('hello', name) if __name__ == '__main__': try: from pytest_cov.embed import cleanup_on_sigterm except ImportError: pass else: cleanup_on_sigterm() p = Process(target=f, args=('bob',)) try: p.start() finally: p.join() # necessary so that the Process exists before the test suite exits (thus coverage is collected) .. _cleanup_on_sigterm: If you got custom signal handling ================================= **pytest-cov 2.6** has a rudimentary ``pytest_cov.embed.cleanup_on_sigterm`` you can use to register a SIGTERM handler that flushes the coverage data. **pytest-cov 2.7** adds a ``pytest_cov.embed.cleanup_on_signal`` function and changes the implementation to be more robust: the handler will call the previous handler (if you had previously registered any), and is re-entrant (will defer extra signals if delivered while the handler runs). For example, if you reload on SIGHUP you should have something like this: .. code-block:: python import os import signal def restart_service(frame, signum): os.exec( ... ) # or whatever your custom signal would do signal.signal(signal.SIGHUP, restart_service) try: from pytest_cov.embed import cleanup_on_signal except ImportError: pass else: cleanup_on_signal(signal.SIGHUP) Note that both ``cleanup_on_signal`` and ``cleanup_on_sigterm`` will run the previous signal handler. Alternatively you can do this: .. code-block:: python import os import signal try: from pytest_cov.embed import cleanup except ImportError: cleanup = None def restart_service(frame, signum): if cleanup is not None: cleanup() os.exec( ... ) # or whatever your custom signal would do signal.signal(signal.SIGHUP, restart_service) If you use Windows ================== On Windows you can register a handler for SIGTERM but it doesn't actually work. It will work if you `os.kill(os.getpid(), signal.SIGTERM)` (send SIGTERM to the current process) but for most intents and purposes that's completely useless. Consequently this means that if you use multiprocessing you got no choice but to use the close/join pattern as described above. Using the context manager API or `terminate` won't work as it relies on SIGTERM. However you can have a working handler for SIGBREAK (with some caveats): .. code-block:: python import os import signal def shutdown(frame, signum): # your app's shutdown or whatever signal.signal(signal.SIGBREAK, shutdown) try: from pytest_cov.embed import cleanup_on_signal except ImportError: pass else: cleanup_on_signal(signal.SIGBREAK) The `caveats `_ being roughly: * you need to deliver ``signal.CTRL_BREAK_EVENT`` * it gets delivered to the whole process group, and that can have unforeseen consequences python-pytest-cov-4.1.0/docs/tox.rst000066400000000000000000000033001444602742700174520ustar00rootroot00000000000000=== Tox === When using `tox `_ you can have ultra-compact configuration - you can have all of it in ``tox.ini``:: [tox] envlist = ... [tool:pytest] ... [coverage:paths] ... [coverage:run] ... [coverage:report] .. [testenv] commands = ... An usual problem users have is that pytest-cov will erase the previous coverage data by default, thus if you run tox with multiple environments you'll get incomplete coverage at the end. To prevent this problem you need to use ``--cov-append``. It's still recommended to clean the previous coverage data to have consistent output. A ``tox.ini`` like this should be enough for sequential runs:: [tox] envlist = clean,py27,py36,... [testenv] commands = pytest --cov --cov-append --cov-report=term-missing ... deps = pytest pytest-cov [testenv:clean] deps = coverage skip_install = true commands = coverage erase For parallel runs we need to set some dependencies and have an extra report env like so:: [tox] envlist = clean,py27,py36,report [testenv] commands = pytest --cov --cov-append --cov-report=term-missing deps = pytest pytest-cov depends = {py27,py36}: clean report: py27,py36 [testenv:report] deps = coverage skip_install = true commands = coverage report coverage html [testenv:clean] deps = coverage skip_install = true commands = coverage erase Depending on your project layout you might need extra configuration, see the working examples at https://github.com/pytest-dev/pytest-cov/tree/master/examples for two common layouts. python-pytest-cov-4.1.0/docs/xdist.rst000066400000000000000000000054311444602742700200020ustar00rootroot00000000000000=========================== Distributed testing (xdist) =========================== "load" mode =========== Distributed testing with dist mode set to "load" will report on the combined coverage of all workers. The workers may be spread out over any number of hosts and each worker may be located anywhere on the file system. Each worker will have its subprocesses measured. Running distributed testing with dist mode set to load:: pytest --cov=myproj -n 2 tests/ Shows a terminal report:: -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94% Again but spread over different hosts and different directories:: pytest --cov=myproj --dist load --tx ssh=memedough@host1//chdir=testenv1 --tx ssh=memedough@host2//chdir=/tmp/testenv2//python=/tmp/env1/bin/python --rsyncdir myproj --rsyncdir tests --rsync examples tests/ Shows a terminal report:: -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover ---------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94% "each" mode =========== Distributed testing with dist mode set to each will report on the combined coverage of all workers. Since each worker is running all tests this allows generating a combined coverage report for multiple environments. Running distributed testing with dist mode set to each:: pytest --cov=myproj --dist each --tx popen//chdir=/tmp/testenv3//python=/usr/local/python27/bin/python --tx ssh=memedough@host2//chdir=/tmp/testenv4//python=/tmp/env2/bin/python --rsyncdir myproj --rsyncdir tests --rsync examples tests/ Shows a terminal report:: ---------------------------------------- coverage ---------------------------------------- platform linux2, python 2.6.5-final-0 platform linux2, python 2.7.0-final-0 Name Stmts Miss Cover ---------------------------------------- myproj/__init__ 2 0 100% myproj/myproj 257 13 94% myproj/feature4286 94 7 92% ---------------------------------------- TOTAL 353 20 94% python-pytest-cov-4.1.0/examples/000077500000000000000000000000001444602742700170005ustar00rootroot00000000000000python-pytest-cov-4.1.0/examples/README.rst000066400000000000000000000010341444602742700204650ustar00rootroot00000000000000Simple examples with ``tox.ini`` ================================ These examples provide necessary configuration to: * aggregate coverage from multiple interpreters * support tox parallel mode * run tests on installed code The `adhoc` layout is the old and problematic layout where you can mix up the installed code with the source code. However, these examples will provide correct configuration even for the `adhoc` layout. The `src` layout configuration is less complicated, have that in mind when picking a layout for your project. python-pytest-cov-4.1.0/examples/adhoc-layout/000077500000000000000000000000001444602742700213715ustar00rootroot00000000000000python-pytest-cov-4.1.0/examples/adhoc-layout/.coveragerc000066400000000000000000000002501444602742700235070ustar00rootroot00000000000000[paths] source = ../example */site-packages/example [run] branch = true parallel = true source = example . [report] show_missing = true precision = 2 python-pytest-cov-4.1.0/examples/adhoc-layout/example/000077500000000000000000000000001444602742700230245ustar00rootroot00000000000000python-pytest-cov-4.1.0/examples/adhoc-layout/example/__init__.py000066400000000000000000000003271444602742700251370ustar00rootroot00000000000000import platform # test merging multiple tox runs with a platform # based branch if platform.python_implementation() == "PyPy": def add(a, b): return a + b else: def add(a, b): return a + b python-pytest-cov-4.1.0/examples/adhoc-layout/setup.py000066400000000000000000000002201444602742700230750ustar00rootroot00000000000000from setuptools import find_packages from setuptools import setup setup( name='example', packages=find_packages(include=['example']) ) python-pytest-cov-4.1.0/examples/adhoc-layout/tests/000077500000000000000000000000001444602742700225335ustar00rootroot00000000000000python-pytest-cov-4.1.0/examples/adhoc-layout/tests/test_example.py000066400000000000000000000001511444602742700255740ustar00rootroot00000000000000import example def test_add(): assert example.add(1, 1) == 2 assert not example.add(0, 1) == 2 python-pytest-cov-4.1.0/examples/adhoc-layout/tox.ini000066400000000000000000000013471444602742700227110ustar00rootroot00000000000000[tox] envlist = pypy3,py39,report [tool:pytest] addopts = --cov-report=term-missing [testenv] setenv = py{py3,39}: COVERAGE_FILE = .coverage.{envname} commands = pytest --cov --cov-config={toxinidir}/.coveragerc {posargs:-vv} deps = pytest coverage # Note: # This is here just to allow examples to be tested against # the current code of pytest-cov. If you copy this then # use "pytest-cov" instead of "../.." ../.. depends = report: pypy3,py39 # note that this is necessary to prevent the tests importing the code from your badly laid project changedir = tests [testenv:report] skip_install = true deps = coverage commands = coverage combine coverage html coverage report --fail-under=100 python-pytest-cov-4.1.0/examples/src-layout/000077500000000000000000000000001444602742700211025ustar00rootroot00000000000000python-pytest-cov-4.1.0/examples/src-layout/.coveragerc000066400000000000000000000002331444602742700232210ustar00rootroot00000000000000[paths] source = src */site-packages [run] branch = true parallel = true source = example tests [report] show_missing = true precision = 2 python-pytest-cov-4.1.0/examples/src-layout/setup.py000066400000000000000000000002401444602742700226100ustar00rootroot00000000000000from setuptools import find_packages from setuptools import setup setup( name='example', packages=find_packages('src'), package_dir={'': 'src'}, ) python-pytest-cov-4.1.0/examples/src-layout/src/000077500000000000000000000000001444602742700216715ustar00rootroot00000000000000python-pytest-cov-4.1.0/examples/src-layout/src/example/000077500000000000000000000000001444602742700233245ustar00rootroot00000000000000python-pytest-cov-4.1.0/examples/src-layout/src/example/__init__.py000066400000000000000000000003271444602742700254370ustar00rootroot00000000000000import platform # test merging multiple tox runs with a platform # based branch if platform.python_implementation() == "PyPy": def add(a, b): return a + b else: def add(a, b): return a + b python-pytest-cov-4.1.0/examples/src-layout/tests/000077500000000000000000000000001444602742700222445ustar00rootroot00000000000000python-pytest-cov-4.1.0/examples/src-layout/tests/test_example.py000066400000000000000000000001511444602742700253050ustar00rootroot00000000000000import example def test_add(): assert example.add(1, 1) == 2 assert not example.add(0, 1) == 2 python-pytest-cov-4.1.0/examples/src-layout/tox.ini000066400000000000000000000011361444602742700224160ustar00rootroot00000000000000[tox] envlist = pypy3,py39,report [tool:pytest] testpaths = tests addopts = --cov-report=term-missing [testenv] setenv = py{py3,39}: COVERAGE_FILE = .coverage.{envname} commands = pytest --cov {posargs:-vv} deps = pytest coverage # Note: # This is here just to allow examples to be tested against # the current code of pytest-cov. If you copy this then # use "pytest-cov" instead of "../.." ../.. depends = report: pypy3,py39 [testenv:report] skip_install = true deps = coverage commands = coverage combine coverage html coverage report --fail-under=100 python-pytest-cov-4.1.0/setup.cfg000066400000000000000000000005761444602742700170130ustar00rootroot00000000000000[flake8] max-line-length = 140 exclude = .tox,.eggs,ci/templates,build,dist [tool:pytest] testpaths = tests python_files = test_*.py addopts = -ra --strict-markers -p pytester [tool:isort] force_single_line = True line_length = 120 known_first_party = pytest_cov default_section = THIRDPARTY forced_separate = test_pytest_cov skip = .tox,.eggs,ci/templates,build,dist python-pytest-cov-4.1.0/setup.py000077500000000000000000000116151444602742700167030ustar00rootroot00000000000000#!/usr/bin/env python import re from glob import glob from itertools import chain from os.path import basename from os.path import dirname from os.path import join from os.path import splitext from setuptools import Command from setuptools import find_packages from setuptools import setup try: # https://setuptools.pypa.io/en/latest/deprecated/distutils-legacy.html from setuptools.command.build import build except ImportError: from distutils.command.build import build from setuptools.command.develop import develop from setuptools.command.easy_install import easy_install from setuptools.command.install_lib import install_lib def read(*names, **kwargs): with open( join(dirname(__file__), *names), encoding=kwargs.get('encoding', 'utf8') ) as fh: return fh.read() class BuildWithPTH(build): def run(self, *args, **kwargs): super().run(*args, **kwargs) 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, *args, **kwargs): super().run(*args, **kwargs) 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, *args, **kwargs): super().run(*args, **kwargs) 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(super().get_outputs(), self.outputs) class DevelopWithPTH(develop): def run(self, *args, **kwargs): super().run(*args, **kwargs) 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( f"import os, sys;exec({sh.read().replace(' ', ' ')!r})" ) setup( name='pytest-cov', version='4.1.0', license='MIT', description='Pytest plugin for measuring coverage.', long_description='{}\n{}'.format(read('README.rst'), re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst'))), author='Marc Schlaich', author_email='marc.schlaich@gmail.com', url='https://github.com/pytest-dev/pytest-cov', 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 :: MIT License', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development :: Testing', 'Topic :: Utilities', ], project_urls={ 'Documentation': 'https://pytest-cov.readthedocs.io/', 'Changelog': 'https://pytest-cov.readthedocs.io/en/latest/changelog.html', 'Issue Tracker': 'https://github.com/pytest-dev/pytest-cov/issues', }, keywords=[ 'cover', 'coverage', 'pytest', 'py.test', 'distributed', 'parallel', ], install_requires=[ 'pytest>=4.6', 'coverage[toml]>=5.2.1' ], python_requires='>=3.7', extras_require={ 'testing': [ 'fields', 'hunter', 'process-tests', 'six', 'pytest-xdist', 'virtualenv', ] }, entry_points={ 'pytest11': [ 'pytest_cov = pytest_cov.plugin', ], }, cmdclass={ 'build': BuildWithPTH, 'easy_install': EasyInstallWithPTH, 'install_lib': InstallLibWithPTH, 'develop': DevelopWithPTH, 'genpth': GeneratePTH, }, ) python-pytest-cov-4.1.0/src/000077500000000000000000000000001444602742700157515ustar00rootroot00000000000000python-pytest-cov-4.1.0/src/pytest-cov.embed000066400000000000000000000006511444602742700210660ustar00rootroot00000000000000if '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 ) ) python-pytest-cov-4.1.0/src/pytest-cov.pth000066400000000000000000000005711444602742700206060ustar00rootroot00000000000000import 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') python-pytest-cov-4.1.0/src/pytest_cov/000077500000000000000000000000001444602742700201505ustar00rootroot00000000000000python-pytest-cov-4.1.0/src/pytest_cov/__init__.py000066400000000000000000000001351444602742700222600ustar00rootroot00000000000000"""pytest-cov: avoid already-imported warning: PYTEST_DONT_REWRITE.""" __version__ = '4.1.0' python-pytest-cov-4.1.0/src/pytest_cov/compat.py000066400000000000000000000010601444602742700220020ustar00rootroot00000000000000try: from StringIO import StringIO except ImportError: from io import StringIO StringIO # pyflakes, this is for re-export class SessionWrapper: 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) python-pytest-cov-4.1.0/src/pytest_cov/embed.py000066400000000000000000000067441444602742700216110ustar00rootroot00000000000000"""Activate coverage at python startup if appropriate. The python site initialisation will ensure that anything we import will be removed and not visible at the end of python startup. However we minimise all work by putting these init actions in this separate module and only importing what is needed when needed. For normal python startup when coverage should not be activated the pth file checks a single env var and does not import or call the init fn here. For python startup when an ancestor process has set the env indicating that code coverage is being collected we activate coverage based on info passed via env vars. """ import atexit import os import signal _active_cov = None def 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 cov_context = os.environ.get('COV_CORE_CONTEXT') if cov_datafile: if _active_cov: cleanup() # Import what we need to activate coverage. import coverage # Determine all source roots. if cov_source in os.pathsep: cov_source = None else: cov_source = cov_source.split(os.pathsep) if cov_config == os.pathsep: cov_config = True # Activate coverage for this process. cov = _active_cov = coverage.Coverage( source=cov_source, branch=cov_branch, data_suffix=True, config_file=cov_config, auto_data=True, data_file=cov_datafile ) cov.load() cov.start() if cov_context: cov.switch_context(cov_context) cov._warn_no_data = False cov._warn_unimported_source = False return cov def _cleanup(cov): if cov is not None: cov.stop() cov.save() cov._auto_save = False # prevent autosaving from cov._atexit in case the interpreter lacks atexit.unregister try: atexit.unregister(cov._atexit) except Exception: pass def cleanup(): global _active_cov global _cleanup_in_progress global _pending_signal _cleanup_in_progress = True _cleanup(_active_cov) _active_cov = None _cleanup_in_progress = False if _pending_signal: pending_signal = _pending_signal _pending_signal = None _signal_cleanup_handler(*pending_signal) _previous_handlers = {} _pending_signal = None _cleanup_in_progress = False def _signal_cleanup_handler(signum, frame): global _pending_signal if _cleanup_in_progress: _pending_signal = signum, frame return cleanup() _previous_handler = _previous_handlers.get(signum) if _previous_handler == signal.SIG_IGN: return elif _previous_handler and _previous_handler is not _signal_cleanup_handler: _previous_handler(signum, frame) elif signum == signal.SIGTERM: os._exit(128 + signum) elif signum == signal.SIGINT: raise KeyboardInterrupt() def cleanup_on_signal(signum): previous = signal.getsignal(signum) if previous is not _signal_cleanup_handler: _previous_handlers[signum] = previous signal.signal(signum, _signal_cleanup_handler) def cleanup_on_sigterm(): cleanup_on_signal(signal.SIGTERM) python-pytest-cov-4.1.0/src/pytest_cov/engine.py000066400000000000000000000370101444602742700217700ustar00rootroot00000000000000"""Coverage controllers for use by pytest-cov and nose-cov.""" import contextlib import copy import functools import os import random import socket import sys import coverage from coverage.data import CoverageData from .compat import StringIO from .embed import cleanup class _NullFile: @staticmethod def write(v): pass @contextlib.contextmanager def _backup(obj, attr): backup = getattr(obj, attr) try: setattr(obj, attr, copy.copy(backup)) yield finally: setattr(obj, attr, backup) def _ensure_topdir(meth): @functools.wraps(meth) def ensure_topdir_wrapper(self, *args, **kwargs): try: original_cwd = os.getcwd() except OSError: # Looks like it's gone, this is non-ideal because a side-effect will # be introduced in the tests here but we can't do anything about it. original_cwd = None os.chdir(self.topdir) try: return meth(self, *args, **kwargs) finally: if original_cwd is not None: os.chdir(original_cwd) return ensure_topdir_wrapper class CovController: """Base class for different plugin implementations.""" def __init__(self, cov_source, cov_report, cov_config, cov_append, cov_branch, config=None, nodeid=None): """Get some common config used by multiple derived classes.""" self.cov_source = cov_source self.cov_report = cov_report self.cov_config = cov_config self.cov_append = cov_append self.cov_branch = cov_branch self.config = config self.nodeid = nodeid self.cov = None self.combining_cov = None self.data_file = None self.node_descs = set() self.failed_workers = [] self.topdir = os.getcwd() self.is_collocated = None @contextlib.contextmanager def ensure_topdir(self): original_cwd = os.getcwd() os.chdir(self.topdir) yield os.chdir(original_cwd) @_ensure_topdir def pause(self): self.cov.stop() self.unset_env() @_ensure_topdir def resume(self): self.cov.start() self.set_env() @_ensure_topdir def set_env(self): """Put info about coverage into the env so that subprocesses can activate coverage.""" if self.cov_source is None: os.environ['COV_CORE_SOURCE'] = os.pathsep else: os.environ['COV_CORE_SOURCE'] = os.pathsep.join(self.cov_source) config_file = os.path.abspath(self.cov_config) if os.path.exists(config_file): os.environ['COV_CORE_CONFIG'] = config_file else: os.environ['COV_CORE_CONFIG'] = os.pathsep os.environ['COV_CORE_DATAFILE'] = os.path.abspath(self.cov.config.data_file) if self.cov_branch: os.environ['COV_CORE_BRANCH'] = 'enabled' @staticmethod def unset_env(): """Remove coverage info from env.""" os.environ.pop('COV_CORE_SOURCE', None) os.environ.pop('COV_CORE_CONFIG', None) os.environ.pop('COV_CORE_DATAFILE', None) os.environ.pop('COV_CORE_BRANCH', None) os.environ.pop('COV_CORE_CONTEXT', None) @staticmethod def get_node_desc(platform, version_info): """Return a description of this node.""" return 'platform {}, python {}'.format(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 = f'{s * sep_len} {txt} {s * (sep_len + sep_extra)}\n' stream.write(out) @_ensure_topdir def summary(self, stream): """Produce coverage reports.""" total = None if not self.cov_report: with _backup(self.cov, "config"): return self.cov.report(show_missing=True, ignore_errors=True, file=_NullFile) # Output coverage section header. if len(self.node_descs) == 1: self.sep(stream, '-', f"coverage: {''.join(self.node_descs)}") else: self.sep(stream, '-', 'coverage') for node_desc in sorted(self.node_descs): self.sep(stream, ' ', f'{node_desc}') # Report on any failed workers. if self.failed_workers: self.sep(stream, '-', 'coverage: failed workers') stream.write('The following workers failed to return coverage data, ' 'ensure that pytest-cov is installed on these workers.\n') for node in self.failed_workers: stream.write(f'{node.gateway.id}\n') # Produce terminal report if wanted. if any(x in self.cov_report for x in ['term', 'term-missing']): options = { 'show_missing': ('term-missing' in self.cov_report) or None, 'ignore_errors': True, 'file': stream, } skip_covered = isinstance(self.cov_report, dict) and 'skip-covered' in self.cov_report.values() options.update({'skip_covered': skip_covered or None}) with _backup(self.cov, "config"): total = self.cov.report(**options) # Produce annotated source code report if wanted. if 'annotate' in self.cov_report: annotate_dir = self.cov_report['annotate'] with _backup(self.cov, "config"): self.cov.annotate(ignore_errors=True, directory=annotate_dir) # We need to call Coverage.report here, just to get the total # Coverage.annotate don't return any total and we need it for --cov-fail-under. with _backup(self.cov, "config"): total = self.cov.report(ignore_errors=True, file=_NullFile) if annotate_dir: stream.write(f'Coverage annotated source written to dir {annotate_dir}\n') else: stream.write('Coverage annotated source written next to source\n') # Produce html report if wanted. if 'html' in self.cov_report: output = self.cov_report['html'] with _backup(self.cov, "config"): total = self.cov.html_report(ignore_errors=True, directory=output) stream.write(f'Coverage HTML written to dir {self.cov.config.html_dir if output is None else output}\n') # Produce xml report if wanted. if 'xml' in self.cov_report: output = self.cov_report['xml'] with _backup(self.cov, "config"): total = self.cov.xml_report(ignore_errors=True, outfile=output) stream.write(f'Coverage XML written to file {self.cov.config.xml_output if output is None else output}\n') # Produce json report if wanted if 'json' in self.cov_report: output = self.cov_report['json'] with _backup(self.cov, "config"): total = self.cov.json_report(ignore_errors=True, outfile=output) stream.write('Coverage JSON written to file %s\n' % (self.cov.config.json_output if output is None else output)) # Produce lcov report if wanted. if 'lcov' in self.cov_report: output = self.cov_report['lcov'] with _backup(self.cov, "config"): self.cov.lcov_report(ignore_errors=True, outfile=output) # We need to call Coverage.report here, just to get the total # Coverage.lcov_report doesn't return any total and we need it for --cov-fail-under. total = self.cov.report(ignore_errors=True, file=_NullFile) stream.write(f'Coverage LCOV written to file {self.cov.config.lcov_output if output is None else output}\n') return total class Central(CovController): """Implementation for centralised operation.""" @_ensure_topdir def start(self): cleanup() self.cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, data_suffix=True, config_file=self.cov_config) self.combining_cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, data_suffix=True, data_file=os.path.abspath(self.cov.config.data_file), config_file=self.cov_config) # Erase or load any previous coverage data and start coverage. if not self.cov_append: self.cov.erase() self.cov.start() self.set_env() @_ensure_topdir def finish(self): """Stop coverage, save data to file and set the list of coverage objects to report on.""" self.unset_env() self.cov.stop() self.cov.save() self.cov = self.combining_cov self.cov.load() self.cov.combine() self.cov.save() node_desc = self.get_node_desc(sys.platform, sys.version_info) self.node_descs.add(node_desc) class DistMaster(CovController): """Implementation for distributed master.""" @_ensure_topdir def start(self): cleanup() # Ensure coverage rc file rsynced if appropriate. if self.cov_config and os.path.exists(self.cov_config): # rsyncdir is going away in pytest-xdist 4.0, already deprecated if hasattr(self.config.option, 'rsyncdir'): self.config.option.rsyncdir.append(self.cov_config) self.cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, data_suffix=True, config_file=self.cov_config) self.cov._warn_no_data = False self.cov._warn_unimported_source = False self.cov._warn_preimported_source = False self.combining_cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, data_suffix=True, data_file=os.path.abspath(self.cov.config.data_file), config_file=self.cov_config) if not self.cov_append: self.cov.erase() self.cov.start() self.cov.config.paths['source'] = [self.topdir] def configure_node(self, node): """Workers need to know if they are collocated and what files have moved.""" node.workerinput.update({ 'cov_master_host': socket.gethostname(), 'cov_master_topdir': self.topdir, 'cov_master_rsync_roots': [str(root) for root in node.nodemanager.roots], }) def testnodedown(self, node, error): """Collect data file name from worker.""" # If worker doesn't return any data then it is likely that this # plugin didn't get activated on the worker side. output = getattr(node, 'workeroutput', {}) if 'cov_worker_node_id' not in output: self.failed_workers.append(node) return # If worker is not collocated then we must save the data file # that it returns to us. if 'cov_worker_data' in output: data_suffix = '%s.%s.%06d.%s' % ( socket.gethostname(), os.getpid(), random.randint(0, 999999), output['cov_worker_node_id'] ) cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, data_suffix=data_suffix, config_file=self.cov_config) cov.start() if coverage.version_info < (5, 0): data = CoverageData() data.read_fileobj(StringIO(output['cov_worker_data'])) cov.data.update(data) else: data = CoverageData(no_disk=True) data.loads(output['cov_worker_data']) cov.get_data().update(data) cov.stop() cov.save() path = output['cov_worker_path'] self.cov.config.paths['source'].append(path) # Record the worker types that contribute to the data file. rinfo = node.gateway._rinfo() node_desc = self.get_node_desc(rinfo.platform, rinfo.version_info) self.node_descs.add(node_desc) @_ensure_topdir def finish(self): """Combines coverage data and sets the list of coverage objects to report on.""" # Combine all the suffix files into the data file. self.cov.stop() self.cov.save() self.cov = self.combining_cov self.cov.load() self.cov.combine() self.cov.save() class DistWorker(CovController): """Implementation for distributed workers.""" @_ensure_topdir def start(self): cleanup() # Determine whether we are collocated with master. self.is_collocated = (socket.gethostname() == self.config.workerinput['cov_master_host'] and self.topdir == self.config.workerinput['cov_master_topdir']) # If we are not collocated then rewrite master paths to worker paths. if not self.is_collocated: master_topdir = self.config.workerinput['cov_master_topdir'] worker_topdir = self.topdir if self.cov_source is not None: self.cov_source = [source.replace(master_topdir, worker_topdir) for source in self.cov_source] self.cov_config = self.cov_config.replace(master_topdir, worker_topdir) # Erase any previous data and start coverage. self.cov = coverage.Coverage(source=self.cov_source, branch=self.cov_branch, data_suffix=True, config_file=self.cov_config) self.cov.start() self.set_env() @_ensure_topdir def finish(self): """Stop coverage and send relevant info back to the master.""" self.unset_env() self.cov.stop() if self.is_collocated: # We don't combine data if we're collocated - we can get # race conditions in the .combine() call (it's not atomic) # The data is going to be combined in the master. self.cov.save() # If we are collocated then just inform the master of our # data file to indicate that we have finished. self.config.workeroutput['cov_worker_node_id'] = self.nodeid else: self.cov.combine() self.cov.save() # If we are not collocated then add the current path # and coverage data to the output so we can combine # it on the master node. # Send all the data to the master over the channel. if coverage.version_info < (5, 0): buff = StringIO() self.cov.data.write_fileobj(buff) data = buff.getvalue() else: data = self.cov.get_data().dumps() self.config.workeroutput.update({ 'cov_worker_path': self.topdir, 'cov_worker_node_id': self.nodeid, 'cov_worker_data': data, }) def summary(self, stream): """Only the master reports so do nothing.""" pass python-pytest-cov-4.1.0/src/pytest_cov/plugin.py000066400000000000000000000356441444602742700220340ustar00rootroot00000000000000"""Coverage plugin for pytest.""" import argparse import os import warnings import coverage import pytest from . import compat from . import embed class CoverageError(Exception): """Indicates that our coverage is too low""" class PytestCovWarning(pytest.PytestWarning): """ The base for all pytest-cov warnings, never raised directly """ class CovDisabledWarning(PytestCovWarning): """Indicates that Coverage was manually disabled""" class CovReportWarning(PytestCovWarning): """Indicates that we failed to generate a report""" def validate_report(arg): file_choices = ['annotate', 'html', 'xml', 'json', 'lcov'] 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 = f'invalid choice: "{arg}" (choose from "{all_choices}")' raise argparse.ArgumentTypeError(msg) if report_type == 'lcov' and coverage.version_info <= (6, 3): raise argparse.ArgumentTypeError('LCOV output is only supported with coverage.py >= 6.3') if len(values) == 1: return report_type, None report_modifier = values[1] if report_type in term_choices and report_modifier in term_modifier_choices: return report_type, report_modifier if report_type not in file_choices: msg = 'output specifier not supported for: "{}" (choose from "{}")'.format(arg, file_choices) raise argparse.ArgumentTypeError(msg) return values def validate_fail_under(num_str): try: value = int(num_str) except ValueError: try: value = float(num_str) except ValueError: raise argparse.ArgumentTypeError('An integer or float value is required.') if value > 100: raise argparse.ArgumentTypeError('Your desire for over-achievement is admirable but misplaced. ' 'The maximum value is 100. Perhaps write more integration tests?') return value def validate_context(arg): if coverage.version_info <= (5, 0): raise argparse.ArgumentTypeError('Contexts are only supported with coverage.py >= 5.x') if arg != "test": raise argparse.ArgumentTypeError('The only supported value is "test".') return arg class StoreReport(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): report_type, file = values namespace.cov_report[report_type] = file def pytest_addoption(parser): """Add options to control coverage.""" group = parser.getgroup( 'cov', 'coverage reporting with distributed testing support') group.addoption('--cov', action='append', default=[], metavar='SOURCE', nargs='?', const=True, dest='cov_source', help='Path or package name to measure during execution (multi-allowed). ' 'Use --cov= to not do any source filtering and record everything.') group.addoption('--cov-reset', action='store_const', const=[], dest='cov_source', help='Reset cov sources accumulated in options so far. ') group.addoption('--cov-report', action=StoreReport, default={}, metavar='TYPE', type=validate_report, help='Type of report to generate: term, term-missing, ' 'annotate, html, xml, json, lcov (multi-allowed). ' 'term, term-missing may be followed by ":skip-covered". ' 'annotate, html, xml, json and lcov may be followed by ":DEST" ' 'where DEST specifies the output location. ' 'Use --cov-report= to not generate any output.') group.addoption('--cov-config', action='store', default='.coveragerc', metavar='PATH', help='Config file for coverage. Default: .coveragerc') group.addoption('--no-cov-on-fail', action='store_true', default=False, help='Do not report coverage if test run fails. ' 'Default: False') group.addoption('--no-cov', action='store_true', default=False, help='Disable coverage report completely (useful for debuggers). ' 'Default: False') group.addoption('--cov-fail-under', action='store', metavar='MIN', type=validate_fail_under, help='Fail if the total coverage is less than MIN.') group.addoption('--cov-append', action='store_true', default=False, help='Do not delete coverage but append to current. ' 'Default: False') group.addoption('--cov-branch', action='store_true', default=None, help='Enable branch coverage.') group.addoption('--cov-context', action='store', metavar='CONTEXT', type=validate_context, help='Dynamic contexts to use. "test" for now.') def _prepare_cov_source(cov_source): """ Prepare cov_source so that: --cov --cov=foobar is equivalent to --cov (cov_source=None) --cov=foo --cov=bar is equivalent to cov_source=['foo', 'bar'] """ return None if True in cov_source else [path for path in cov_source if path is not True] @pytest.hookimpl(tryfirst=True) def pytest_load_initial_conftests(early_config, parser, args): options = early_config.known_args_namespace no_cov = options.no_cov_should_warn = False for arg in args: arg = str(arg) if arg == '--no-cov': no_cov = True elif arg.startswith('--cov') and no_cov: options.no_cov_should_warn = True break if early_config.known_args_namespace.cov_source: plugin = CovPlugin(options, early_config.pluginmanager) early_config.pluginmanager.register(plugin, '_cov') class CovPlugin: """Use coverage package to produce code coverage reports. Delegates all work to a particular implementation based on whether this test process is centralised, a distributed master or a distributed worker. """ def __init__(self, options, pluginmanager, start=True, no_cov_should_warn=False): """Creates a coverage pytest plugin. We read the rc file that coverage uses to get the data file name. This is needed since we give coverage through it's API the data file name. """ # Our implementation is unknown at this time. self.pid = None self.cov_controller = None self.cov_report = compat.StringIO() self.cov_total = None self.failed = False self._started = False self._start_path = None self._disabled = False self.options = options is_dist = (getattr(options, 'numprocesses', False) or getattr(options, 'distload', False) or getattr(options, 'dist', 'no') != 'no') if getattr(options, 'no_cov', False): self._disabled = True return if not self.options.cov_report: self.options.cov_report = ['term'] elif len(self.options.cov_report) == 1 and '' in self.options.cov_report: self.options.cov_report = {} self.options.cov_source = _prepare_cov_source(self.options.cov_source) # import engine lazily here to avoid importing # it for unit tests that don't need it from . import engine if is_dist and start: self.start(engine.DistMaster) elif start: self.start(engine.Central) # worker is started in pytest hook def start(self, controller_cls, config=None, nodeid=None): if config is None: # fake config option for engine class Config: option = self.options config = Config() self.cov_controller = controller_cls( self.options.cov_source, self.options.cov_report, self.options.cov_config, self.options.cov_append, self.options.cov_branch, config, nodeid ) self.cov_controller.start() self._started = True self._start_path = os.getcwd() cov_config = self.cov_controller.cov.config if self.options.cov_fail_under is None and hasattr(cov_config, 'fail_under'): self.options.cov_fail_under = cov_config.fail_under def _is_worker(self, session): return getattr(session.config, 'workerinput', None) is not None def pytest_sessionstart(self, session): """At session start determine our implementation and delegate to it.""" if self.options.no_cov: # Coverage can be disabled because it does not cooperate with debuggers well. self._disabled = True return # import engine lazily here to avoid importing # it for unit tests that don't need it from . import engine self.pid = os.getpid() if self._is_worker(session): nodeid = ( session.config.workerinput.get('workerid', getattr(session, 'nodeid')) ) self.start(engine.DistWorker, session.config, nodeid) elif not self._started: self.start(engine.Central) if self.options.cov_context == 'test': session.config.pluginmanager.register(TestContextPlugin(self.cov_controller.cov), '_cov_contexts') @pytest.hookimpl(optionalhook=True) def pytest_configure_node(self, node): """Delegate to our implementation. Mark this hook as optional in case xdist is not installed. """ if not self._disabled: self.cov_controller.configure_node(node) @pytest.hookimpl(optionalhook=True) def pytest_testnodedown(self, node, error): """Delegate to our implementation. Mark this hook as optional in case xdist is not installed. """ if not self._disabled: self.cov_controller.testnodedown(node, error) def _should_report(self): needed = self.options.cov_report or self.options.cov_fail_under return needed and 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 @pytest.hookimpl(hookwrapper=True) def pytest_runtestloop(self, session): yield if self._disabled: return compat_session = compat.SessionWrapper(session) self.failed = bool(compat_session.testsfailed) if self.cov_controller is not None: self.cov_controller.finish() if not self._is_worker(session) and self._should_report(): # import coverage lazily here to avoid importing # it for unit tests that don't need it from coverage.misc import CoverageException try: self.cov_total = self.cov_controller.summary(self.cov_report) except CoverageException as exc: message = f'Failed to generate report: {exc}\n' session.config.pluginmanager.getplugin("terminalreporter").write( f'WARNING: {message}\n', red=True, bold=True) warnings.warn(CovReportWarning(message)) self.cov_total = 0 assert self.cov_total is not None, 'Test coverage should never be `None`' if self._failed_cov_total() and not self.options.collectonly: # make sure we get the EXIT_TESTSFAILED exit code compat_session.testsfailed += 1 def pytest_terminal_summary(self, terminalreporter): if self._disabled: if self.options.no_cov_should_warn: message = 'Coverage disabled via --no-cov switch!' terminalreporter.write(f'WARNING: {message}\n', red=True, bold=True) warnings.warn(CovDisabledWarning(message)) return if self.cov_controller is None: return if self.cov_total is None: # we shouldn't report, or report generation failed (error raised above) return report = self.cov_report.getvalue() # Avoid undesirable new lines when output is disabled with "--cov-report=". if report: terminalreporter.write('\n' + report + '\n') if self.options.cov_fail_under is not None and self.options.cov_fail_under > 0: failed = self.cov_total < self.options.cov_fail_under markup = {'red': True, 'bold': True} if failed else {'green': True} message = ( '{fail}Required test coverage of {required}% {reached}. ' 'Total coverage: {actual:.2f}%\n' .format( required=self.options.cov_fail_under, actual=self.cov_total, fail="FAIL " if failed else "", reached="not reached" if failed else "reached" ) ) terminalreporter.write(message, **markup) def pytest_runtest_setup(self, item): if os.getpid() != self.pid: # test is run in another process than session, run # coverage manually embed.init() def pytest_runtest_teardown(self, item): embed.cleanup() @pytest.hookimpl(hookwrapper=True) def pytest_runtest_call(self, item): if (item.get_closest_marker('no_cover') or 'no_cover' in getattr(item, 'fixturenames', ())): self.cov_controller.pause() yield self.cov_controller.resume() else: yield class TestContextPlugin: def __init__(self, cov): self.cov = cov def pytest_runtest_setup(self, item): self.switch_context(item, 'setup') def pytest_runtest_teardown(self, item): self.switch_context(item, 'teardown') def pytest_runtest_call(self, item): self.switch_context(item, 'run') def switch_context(self, item, when): context = f"{item.nodeid}|{when}" self.cov.switch_context(context) os.environ['COV_CORE_CONTEXT'] = context @pytest.fixture def no_cover(): """A pytest fixture to disable coverage.""" pass @pytest.fixture def cov(request): """A pytest fixture to provide access to the underlying coverage object.""" # Check with hasplugin to avoid getplugin exception in older pytest. if request.config.pluginmanager.hasplugin('_cov'): plugin = request.config.pluginmanager.getplugin('_cov') if plugin.cov_controller: return plugin.cov_controller.cov return None def pytest_configure(config): config.addinivalue_line("markers", "no_cover: disable coverage for this test.") python-pytest-cov-4.1.0/tests/000077500000000000000000000000001444602742700163245ustar00rootroot00000000000000python-pytest-cov-4.1.0/tests/conftest.py000066400000000000000000000001111444602742700205140ustar00rootroot00000000000000def pytest_configure(config): config.option.runpytest = 'subprocess' python-pytest-cov-4.1.0/tests/contextful.py000066400000000000000000000045711444602742700211000ustar00rootroot00000000000000# A test file for test_pytest_cov.py:test_contexts import unittest import pytest def test_01(): assert 1 == 1 # r1 def test_02(): assert 2 == 2 # r2 class OldStyleTests(unittest.TestCase): items = [] @classmethod def setUpClass(cls): cls.items.append("hello") # s3 @classmethod def tearDownClass(cls): cls.items.pop() # t4 def setUp(self): self.number = 1 # r3 r4 def tearDown(self): self.number = None # r3 r4 def test_03(self): assert self.number == 1 # r3 assert self.items[0] == "hello" # r3 def test_04(self): assert self.number == 1 # r4 assert self.items[0] == "hello" # r4 @pytest.fixture def some_data(): return [1, 2, 3] # s5 s6 def test_05(some_data): assert len(some_data) == 3 # r5 @pytest.fixture def more_data(some_data): return [2*x for x in some_data] # s6 def test_06(some_data, more_data): assert len(some_data) == len(more_data) # r6 @pytest.fixture(scope='session') def expensive_data(): return list(range(10)) # s7 def test_07(expensive_data): assert len(expensive_data) == 10 # r7 def test_08(expensive_data): assert len(expensive_data) == 10 # r8 @pytest.fixture(params=[1, 2, 3]) def parametrized_number(request): return request.param # s9-1 s9-2 s9-3 def test_09(parametrized_number): assert parametrized_number > 0 # r9-1 r9-2 r9-3 def test_10(): assert 1 == 1 # r10 @pytest.mark.parametrize("x, ans", [ (1, 101), (2, 202), ]) def test_11(x, ans): assert 100 * x + x == ans # r11-1 r11-2 @pytest.mark.parametrize("x, ans", [ (1, 101), (2, 202), ], ids=['one', 'two']) def test_12(x, ans): assert 100 * x + x == ans # r12-1 r12-2 @pytest.mark.parametrize("x", [1, 2]) @pytest.mark.parametrize("y", [3, 4]) def test_13(x, y): assert x + y > 0 # r13-1 r13-2 r13-3 r13-4 python-pytest-cov-4.1.0/tests/helper.py000066400000000000000000000000471444602742700201560ustar00rootroot00000000000000def do_stuff(): a = 1 return a python-pytest-cov-4.1.0/tests/test_pytest_cov.py000066400000000000000000001724021444602742700221420ustar00rootroot00000000000000import collections import glob import os import platform import re import subprocess import sys from itertools import chain import coverage import py import pytest import virtualenv import xdist from fields import Namespace from process_tests import TestProcess as _TestProcess from process_tests import dump_on_error from process_tests import wait_for_strings import pytest_cov.plugin try: from StringIO import StringIO except ImportError: from io import StringIO coverage, platform # required for skipif mark on test_cov_min_from_coveragerc max_worker_restart_0 = "--max-worker-restart=0" SCRIPT = ''' import sys, helper def pytest_generate_tests(metafunc): for i in [10]: metafunc.parametrize('p', range(i)) def test_foo(p): x = True helper.do_stuff() # get some coverage in some other completely different location if sys.version_info[0] > 5: assert False ''' SCRIPT2 = ''' # def test_bar(): x = True assert x ''' COVERAGERC_SOURCE = '''\ [run] source = . ''' SCRIPT_CHILD = ''' import sys idx = int(sys.argv[1]) if idx == 0: foo = "a" # previously there was a "pass" here but Python 3.5 optimizes it away. if idx == 1: foo = "b" # previously there was a "pass" here but Python 3.5 optimizes it away. ''' SCRIPT_PARENT = ''' import os import subprocess import sys def pytest_generate_tests(metafunc): for i in [2]: metafunc.parametrize('idx', range(i)) def test_foo(idx): out, err = subprocess.Popen( [sys.executable, os.path.join(os.path.dirname(__file__), 'child_script.py'), str(idx)], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() # there is a issue in coverage.py with multiline statements at # end of file: https://bitbucket.org/ned/coveragepy/issue/293 pass ''' SCRIPT_PARENT_CHANGE_CWD = ''' import subprocess import sys import os def pytest_generate_tests(metafunc): for i in [2]: metafunc.parametrize('idx', range(i)) def test_foo(idx): os.mkdir("foobar") os.chdir("foobar") subprocess.check_call([ sys.executable, os.path.join(os.path.dirname(__file__), 'child_script.py'), str(idx) ]) # there is a issue in coverage.py with multiline statements at # end of file: https://bitbucket.org/ned/coveragepy/issue/293 pass ''' SCRIPT_PARENT_CHANGE_CWD_IMPORT_CHILD = ''' import subprocess import sys import os def pytest_generate_tests(metafunc): for i in [2]: if metafunc.function is test_foo: metafunc.parametrize('idx', range(i)) def test_foo(idx): os.mkdir("foobar") os.chdir("foobar") subprocess.check_call([ sys.executable, '-c', 'import sys; sys.argv = ["", str(%s)]; import child_script' % idx ]) # there is a issue in coverage.py with multiline statements at # end of file: https://bitbucket.org/ned/coveragepy/issue/293 pass ''' SCRIPT_FUNCARG = ''' import coverage def test_foo(cov): assert isinstance(cov, coverage.Coverage) ''' SCRIPT_FUNCARG_NOT_ACTIVE = ''' def test_foo(cov): assert cov is None ''' CHILD_SCRIPT_RESULT = '[56] * 100%' PARENT_SCRIPT_RESULT = '9 * 100%' DEST_DIR = 'cov_dest' XML_REPORT_NAME = 'cov.xml' JSON_REPORT_NAME = 'cov.json' LCOV_REPORT_NAME = 'cov.info' xdist_params = pytest.mark.parametrize('opts', [ '', pytest.param('-n 1', marks=pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"')) ], ids=['nodist', 'xdist']) @pytest.fixture(scope='session', autouse=True) def adjust_sys_path(): """Adjust PYTHONPATH during tests to make "helper" importable in SCRIPT.""" orig_path = os.environ.get('PYTHONPATH', None) new_path = os.path.dirname(__file__) if orig_path is not None: new_path = os.pathsep.join([new_path, orig_path]) os.environ['PYTHONPATH'] = new_path yield if orig_path is None: del os.environ['PYTHONPATH'] else: os.environ['PYTHONPATH'] = orig_path @pytest.fixture(params=[ ('branch=true', '--cov-branch', '9 * 85%', '3 * 100%'), ('branch=true', '', '9 * 85%', '3 * 100%'), ('', '--cov-branch', '9 * 85%', '3 * 100%'), ('', '', '9 * 89%', '3 * 100%'), ], ids=['branch2x', 'branch1c', 'branch1a', 'nobranch']) def prop(request): return Namespace( code=SCRIPT, code2=SCRIPT2, conf=request.param[0], fullconf=f'[run]\n{request.param[0]}\n', prefixedfullconf=f'[coverage:run]\n{request.param[0]}\n', args=request.param[1].split(), result=request.param[2], result2=request.param[3], ) def test_central(pytester, testdir, prop): script = testdir.makepyfile(prop.code) testdir.tmpdir.join('.coveragerc').write(prop.fullconf) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', script, *prop.args) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_central* {prop.result} *', '*10 passed*' ]) assert result.ret == 0 def test_annotate(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={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', f'--cov={script.dirpath()}', '--cov-report=annotate:' + DEST_DIR, script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'Coverage annotated source written to dir ' + DEST_DIR, '*10 passed*', ]) dest_dir = testdir.tmpdir.join(DEST_DIR) assert dest_dir.check(dir=True) assert dest_dir.join(script.basename + ",cover").check() assert result.ret == 0 def test_html(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=html', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'Coverage HTML written to dir htmlcov', '*10 passed*', ]) dest_dir = testdir.tmpdir.join('htmlcov') assert dest_dir.check(dir=True) assert dest_dir.join("index.html").check() assert result.ret == 0 def test_html_output_dir(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=html:' + DEST_DIR, script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'Coverage HTML written to dir ' + DEST_DIR, '*10 passed*', ]) dest_dir = testdir.tmpdir.join(DEST_DIR) assert dest_dir.check(dir=True) assert dest_dir.join("index.html").check() assert result.ret == 0 def test_term_report_does_not_interact_with_html_output(testdir): script = testdir.makepyfile(test_funcarg=SCRIPT_FUNCARG) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing:skip-covered', '--cov-report=html:' + DEST_DIR, script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'Coverage HTML written to dir ' + DEST_DIR, '*1 passed*', ]) dest_dir = testdir.tmpdir.join(DEST_DIR) assert dest_dir.check(dir=True) assert sorted(dest_dir.visit("**/*.html")) == [dest_dir.join("index.html"), dest_dir.join("test_funcarg_py.html")] assert dest_dir.join("index.html").check() assert result.ret == 0 def test_html_configured_output_dir(testdir): script = testdir.makepyfile(SCRIPT) testdir.tmpdir.join('.coveragerc').write(""" [html] directory = somewhere """) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=html', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'Coverage HTML written to dir somewhere', '*10 passed*', ]) dest_dir = testdir.tmpdir.join('somewhere') assert dest_dir.check(dir=True) assert dest_dir.join("index.html").check() assert result.ret == 0 def test_xml_output_dir(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=xml:' + XML_REPORT_NAME, script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'Coverage XML written to file ' + XML_REPORT_NAME, '*10 passed*', ]) assert testdir.tmpdir.join(XML_REPORT_NAME).check() assert result.ret == 0 def test_json_output_dir(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', '--cov=%s' % script.dirpath(), '--cov-report=json:' + JSON_REPORT_NAME, script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'Coverage JSON written to file ' + JSON_REPORT_NAME, '*10 passed*', ]) assert testdir.tmpdir.join(JSON_REPORT_NAME).check() assert result.ret == 0 @pytest.mark.skipif("coverage.version_info < (6, 3)") def test_lcov_output_dir(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=lcov:' + LCOV_REPORT_NAME, script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'Coverage LCOV written to file ' + LCOV_REPORT_NAME, '*10 passed*', ]) assert testdir.tmpdir.join(LCOV_REPORT_NAME).check() assert result.ret == 0 @pytest.mark.skipif("coverage.version_info >= (6, 3)") def test_lcov_not_supported(testdir): script = testdir.makepyfile("a = 1") result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=lcov', script, ) result.stderr.fnmatch_lines([ '*argument --cov-report: LCOV output is only supported with coverage.py >= 6.3', ]) assert result.ret != 0 def test_term_output_dir(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term:' + DEST_DIR, script) result.stderr.fnmatch_lines([ f'*argument --cov-report: output specifier not supported for: "term:{DEST_DIR}"*', ]) assert result.ret != 0 def test_term_missing_output_dir(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing:' + DEST_DIR, script) result.stderr.fnmatch_lines([ '*argument --cov-report: output specifier not supported for: ' '"term-missing:%s"*' % DEST_DIR, ]) assert result.ret != 0 def test_cov_min_100(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={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_100_passes_if_collectonly(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', '--cov-fail-under=100', '--collect-only', script) assert result.ret == 0 def test_cov_min_50(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=html', '--cov-report=xml', '--cov-fail-under=50', script) assert result.ret == 0 result.stdout.fnmatch_lines([ 'Required test coverage of 50% reached. Total coverage: *%' ]) def test_cov_min_float_value(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', '--cov-fail-under=88.88', script) assert result.ret == 0 result.stdout.fnmatch_lines([ 'Required test coverage of 88.88% reached. Total coverage: 88.89%' ]) def test_cov_min_float_value_not_reached(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', '--cov-fail-under=88.89', script) assert result.ret == 1 result.stdout.fnmatch_lines([ 'FAIL Required test coverage of 88.89% not reached. Total coverage: 88.89%' ]) def test_cov_min_no_report(testdir): script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', f'--cov={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(pytester, 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 * -*', f'test_central_nonspecific* {prop.result} *', '*10 passed*' ]) # multi-module coverage report assert any(line.startswith('TOTAL ') for line in result.stdout.lines) assert result.ret == 0 def test_cov_min_from_coveragerc(testdir): script = testdir.makepyfile(SCRIPT) testdir.tmpdir.join('.coveragerc').write(""" [report] fail_under = 100 """) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) assert result.ret != 0 def test_central_coveragerc(pytester, 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 * -*', f'test_central_coveragerc* {prop.result} *', '*10 passed*', ]) assert result.ret == 0 @xdist_params def test_central_with_path_aliasing(pytester, testdir, monkeypatch, opts, prop): mod1 = testdir.mkdir('src').join('mod.py') mod1.write(SCRIPT) mod2 = testdir.mkdir('aliased').join('mod.py') mod2.write(SCRIPT) script = testdir.makepyfile(''' from mod import * ''') testdir.tmpdir.join('setup.cfg').write(f""" [coverage:paths] source = src aliased [coverage:run] source = mod parallel = true {prop.conf} """) monkeypatch.setitem(os.environ, 'PYTHONPATH', os.pathsep.join([os.environ.get('PYTHONPATH', ''), 'aliased'])) result = testdir.runpytest('-v', '-s', '--cov', '--cov-report=term-missing', script, *opts.split()+prop.args) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'src[\\/]mod* {prop.result} *', '*10 passed*', ]) assert result.ret == 0 @xdist_params def test_borken_cwd(pytester, testdir, monkeypatch, opts): testdir.makepyfile(mod=''' def foobar(a, b): return a + b ''') script = testdir.makepyfile(''' import os import tempfile import pytest import mod @pytest.fixture def bad(): path = tempfile.mkdtemp('test_borken_cwd') os.chdir(path) yield try: os.rmdir(path) except OSError: pass def test_foobar(bad): assert mod.foobar(1, 2) == 3 ''') result = testdir.runpytest('-v', '-s', '--cov=mod', '--cov-branch', script, *opts.split()) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', '*mod* 100%', '*1 passed*', ]) assert result.ret == 0 def test_subprocess_with_path_aliasing(pytester, testdir, monkeypatch): src = testdir.mkdir('src') src.join('parent_script.py').write(SCRIPT_PARENT) src.join('child_script.py').write(SCRIPT_CHILD) aliased = testdir.mkdir('aliased') parent_script = aliased.join('parent_script.py') parent_script.write(SCRIPT_PARENT) aliased.join('child_script.py').write(SCRIPT_CHILD) testdir.tmpdir.join('.coveragerc').write(""" [paths] source = src aliased [run] source = parent_script child_script parallel = true """) monkeypatch.setitem(os.environ, 'PYTHONPATH', os.pathsep.join([ os.environ.get('PYTHONPATH', ''), 'aliased'])) result = testdir.runpytest('-v', '--cov', '--cov-report=term-missing', parent_script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'src[\\/]child_script* {CHILD_SCRIPT_RESULT}*', f'src[\\/]parent_script* {PARENT_SCRIPT_RESULT}*', ]) assert result.ret == 0 def test_show_missing_coveragerc(pytester, testdir, prop): script = testdir.makepyfile(prop.code) testdir.tmpdir.join('.coveragerc').write(f""" [run] source = . {prop.conf} [report] show_missing = true """) result = testdir.runpytest('-v', '--cov', '--cov-report=term', script, *prop.args) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'Name * Stmts * Miss * Cover * Missing', f'test_show_missing_coveragerc* {prop.result} * 11*', '*10 passed*', ]) assert result.ret == 0 def test_no_cov_on_fail(testdir): script = testdir.makepyfile(''' def test_fail(): assert False ''') result = testdir.runpytest('-v', f'--cov={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(pytester, testdir, monkeypatch): script = testdir.makepyfile(SCRIPT) testdir.makeini(""" [pytest] addopts=--no-cov """) result = testdir.runpytest('-vvv', f'--cov={script.dirpath()}', '--cov-report=term-missing', '-rw', script) result.stdout.fnmatch_lines_random([ 'WARNING: Coverage disabled via --no-cov switch!', '*Coverage disabled via --no-cov switch!', ]) def test_cov_and_failure_report_on_fail(testdir): script = testdir.makepyfile(SCRIPT + ''' def test_fail(p): assert False ''') result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-fail-under=100', '--cov-report=html', script) result.stdout.fnmatch_lines_random([ '*10 failed*', '*coverage: platform*', '*FAIL Required test coverage of 100% not reached*', '*assert False*', ]) @pytest.mark.skipif('sys.platform == "win32" or platform.python_implementation() == "PyPy"') def test_dist_combine_racecondition(testdir): script = testdir.makepyfile(""" import pytest @pytest.mark.parametrize("foo", range(1000)) def test_foo(foo): """ + "\n".join(f""" if foo == {i}: assert True """ for i in range(1000))) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', '-n', '5', '-s', script) result.stdout.fnmatch_lines([ 'test_dist_combine_racecondition* 0 * 100%*', '*1000 passed*' ]) for line in chain(result.stdout.lines, result.stderr.lines): assert 'The following workers failed to return coverage data' not in line assert 'INTERNALERROR' not in line assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_collocated(pytester, testdir, prop): script = testdir.makepyfile(prop.code) testdir.tmpdir.join('.coveragerc').write(prop.fullconf) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', '--dist=load', '--tx=2*popen', max_worker_restart_0, script, *prop.args) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_dist_collocated* {prop.result} *', '*10 passed*' ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_not_collocated(pytester, testdir, prop): script = testdir.makepyfile(prop.code) dir1 = testdir.mkdir('dir1') dir2 = testdir.mkdir('dir2') testdir.tmpdir.join('.coveragerc').write(f''' [run] {prop.conf} [paths] source = . dir1 dir2''') result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', '--dist=load', f'--tx=popen//chdir={dir1}', f'--tx=popen//chdir={dir2}', f'--rsyncdir={script.basename}', '--rsyncdir=.coveragerc', max_worker_restart_0, '-s', script, *prop.args) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_dist_not_collocated* {prop.result} *', '*10 passed*' ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_not_collocated_coveragerc_source(pytester, testdir, prop): script = testdir.makepyfile(prop.code) dir1 = testdir.mkdir('dir1') dir2 = testdir.mkdir('dir2') testdir.tmpdir.join('.coveragerc').write(f''' [run] {prop.conf} source = {script.dirpath()} [paths] source = . dir1 dir2''') result = testdir.runpytest('-v', '--cov', '--cov-report=term-missing', '--dist=load', f'--tx=popen//chdir={dir1}', f'--tx=popen//chdir={dir2}', f'--rsyncdir={script.basename}', '--rsyncdir=.coveragerc', max_worker_restart_0, '-s', script, *prop.args) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_dist_not_collocated* {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', f'--cov={scripts.dirpath()}', '--cov-report=term-missing', parent_script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'child_script* {CHILD_SCRIPT_RESULT}*', f'parent_script* {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', f'--cov={scripts.dirpath()}', '--cov-config=coveragerc', '--cov-report=term-missing', parent_script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'*child_script* {CHILD_SCRIPT_RESULT}*', '*parent_script* 100%*', ]) assert result.ret == 0 def test_central_subprocess_change_cwd_with_pythonpath(pytester, 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 * -*', f'*child_script* {CHILD_SCRIPT_RESULT}*', ]) assert result.ret == 0 def test_central_subprocess_no_subscript(testdir): script = testdir.makepyfile(""" import subprocess, sys def test_foo(): subprocess.check_call([sys.executable, '-c', 'print("Hello World")']) """) testdir.makefile('', coveragerc=""" [run] parallel = true """) result = testdir.runpytest('-v', '--cov-config=coveragerc', f'--cov={script.dirpath()}', '--cov-branch', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'test_central_subprocess_no_subscript* * 3 * 0 * 100%*', ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_subprocess_collocated(testdir): scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT, child_script=SCRIPT_CHILD) parent_script = scripts.dirpath().join('parent_script.py') result = testdir.runpytest('-v', f'--cov={scripts.dirpath()}', '--cov-report=term-missing', '--dist=load', '--tx=2*popen', max_worker_restart_0, parent_script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'child_script* {CHILD_SCRIPT_RESULT}*', f'parent_script* {PARENT_SCRIPT_RESULT}*', ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_dist_subprocess_not_collocated(pytester, testdir, tmpdir): scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT, child_script=SCRIPT_CHILD) parent_script = scripts.dirpath().join('parent_script.py') child_script = scripts.dirpath().join('child_script.py') dir1 = tmpdir.mkdir('dir1') dir2 = tmpdir.mkdir('dir2') testdir.tmpdir.join('.coveragerc').write(f''' [paths] source = {scripts.dirpath()} */dir1 */dir2 ''') result = testdir.runpytest('-v', f'--cov={scripts.dirpath()}', '--dist=load', f'--tx=popen//chdir={dir1}', f'--tx=popen//chdir={dir2}', f'--rsyncdir={child_script}', f'--rsyncdir={parent_script}', '--rsyncdir=.coveragerc', max_worker_restart_0, parent_script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'child_script* {CHILD_SCRIPT_RESULT}*', f'parent_script* {PARENT_SCRIPT_RESULT}*', ]) assert result.ret == 0 def test_invalid_coverage_source(testdir): script = testdir.makepyfile(SCRIPT) testdir.makeini(""" [pytest] console_output_style=classic """) result = testdir.runpytest('-v', '--cov=non_existent_module', '--cov-report=term-missing', script) result.stdout.fnmatch_lines([ '*10 passed*' ]) result.stderr.fnmatch_lines([ '*No data was collected.*' ]) result.stdout.fnmatch_lines([ '*Failed to generate report: No data to report.', ]) assert result.ret == 0 matching_lines = [line for line in result.outlines if '%' in line] assert not matching_lines @pytest.mark.skipif("'dev' in pytest.__version__") @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') @pytest.mark.skipif('tuple(map(int, xdist.__version__.split("."))) >= (2, 3, 0)', reason="Since pytest-xdist 2.3.0 the parent sys.path is copied in the child process") def test_dist_missing_data(testdir): """Test failure when using a worker without pytest-cov installed.""" venv_path = os.path.join(str(testdir.tmpdir), 'venv') virtualenv.cli_run([venv_path]) if sys.platform == 'win32': if platform.python_implementation() == "PyPy": exe = os.path.join(venv_path, 'bin', 'python.exe') else: exe = os.path.join(venv_path, 'Scripts', 'python.exe') else: exe = os.path.join(venv_path, 'bin', 'python') subprocess.check_call([ exe, '-mpip', 'install', f'py=={py.__version__}', f'pytest=={pytest.__version__}', f'pytest_xdist=={xdist.__version__}' ]) script = testdir.makepyfile(SCRIPT) result = testdir.runpytest('-v', '--assert=plain', f'--cov={script.dirpath()}', '--cov-report=term-missing', '--dist=load', f'--tx=popen//python={exe}', max_worker_restart_0, str(script)) result.stdout.fnmatch_lines([ 'The following workers failed to return coverage data, ensure that pytest-cov is installed on these workers.' ]) def test_funcarg(testdir): script = testdir.makepyfile(SCRIPT_FUNCARG) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'test_funcarg* 3 * 100%*', '*1 passed*' ]) assert result.ret == 0 def test_funcarg_not_active(testdir): script = testdir.makepyfile(SCRIPT_FUNCARG_NOT_ACTIVE) result = testdir.runpytest('-v', script) result.stdout.fnmatch_lines([ '*1 passed*' ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32"', reason="SIGTERM isn't really supported on Windows") @pytest.mark.xfail('platform.python_implementation() == "PyPy"', reason="Interpreter seems buggy") def test_cleanup_on_sigterm(testdir): script = testdir.makepyfile(''' import os, signal, subprocess, sys, time def cleanup(num, frame): print("num == signal.SIGTERM => %s" % (num == signal.SIGTERM)) raise Exception() def test_run(): proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) time.sleep(1) proc.terminate() stdout, stderr = proc.communicate() assert not stderr assert stdout == b"""num == signal.SIGTERM => True captured Exception() """ assert proc.returncode == 0 if __name__ == "__main__": signal.signal(signal.SIGTERM, cleanup) from pytest_cov.embed import cleanup_on_sigterm cleanup_on_sigterm() try: time.sleep(10) except BaseException as exc: print("captured %r" % exc) ''') result = testdir.runpytest('-vv', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'test_cleanup_on_sigterm* 26-27', '*1 passed*' ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform != "win32"') @pytest.mark.parametrize('setup', [ ('signal.signal(signal.SIGBREAK, signal.SIG_DFL); cleanup_on_signal(signal.SIGBREAK)', '87% 21-22'), ('cleanup_on_signal(signal.SIGBREAK)', '87% 21-22'), ('cleanup()', '73% 19-22'), ]) def test_cleanup_on_sigterm_sig_break(pytester, testdir, setup): # worth a read: https://stefan.sofa-rockers.org/2013/08/15/handling-sub-process-hierarchies-python-linux-os-x/ script = testdir.makepyfile(''' import os, signal, subprocess, sys, time def test_run(): proc = subprocess.Popen( [sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, shell=True ) time.sleep(1) proc.send_signal(signal.CTRL_BREAK_EVENT) stdout, stderr = proc.communicate() assert not stderr assert stdout in [b"^C", b"", b"captured IOError(4, 'Interrupted function call')\\n"] if __name__ == "__main__": from pytest_cov.embed import cleanup_on_signal, cleanup ''' + setup[0] + ''' try: time.sleep(10) except BaseException as exc: print("captured %r" % exc) ''') result = testdir.runpytest('-vv', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_cleanup_on_sigterm* {setup[1]}', '*1 passed*' ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32"', reason="SIGTERM isn't really supported on Windows") @pytest.mark.xfail('sys.platform == "darwin"', reason="Something weird going on Macs...") @pytest.mark.xfail('platform.python_implementation() == "PyPy"', reason="Interpreter seems buggy") @pytest.mark.parametrize('setup', [ ('signal.signal(signal.SIGTERM, signal.SIG_DFL); cleanup_on_sigterm()', '88% 18-19'), ('cleanup_on_sigterm()', '88% 18-19'), ('cleanup()', '75% 16-19'), ]) def test_cleanup_on_sigterm_sig_dfl(pytester, testdir, setup): script = testdir.makepyfile(''' import os, signal, subprocess, sys, time def test_run(): proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) time.sleep(1) proc.terminate() stdout, stderr = proc.communicate() assert not stderr assert stdout == b"" assert proc.returncode in [128 + signal.SIGTERM, -signal.SIGTERM] if __name__ == "__main__": from pytest_cov.embed import cleanup_on_sigterm, cleanup ''' + setup[0] + ''' try: time.sleep(10) except BaseException as exc: print("captured %r" % exc) ''') result = testdir.runpytest('-vv', '--assert=plain', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_cleanup_on_sigterm* {setup[1]}', '*1 passed*' ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32"', reason="SIGINT is subtly broken on Windows") @pytest.mark.xfail('sys.platform == "darwin"', reason="Something weird going on Macs...") @pytest.mark.xfail('platform.python_implementation() == "PyPy"', reason="Interpreter seems buggy") def test_cleanup_on_sigterm_sig_dfl_sigint(testdir): script = testdir.makepyfile(''' import os, signal, subprocess, sys, time def test_run(): proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) time.sleep(1) proc.send_signal(signal.SIGINT) stdout, stderr = proc.communicate() assert not stderr assert stdout == b"""captured KeyboardInterrupt() """ assert proc.returncode == 0 if __name__ == "__main__": from pytest_cov.embed import cleanup_on_signal cleanup_on_signal(signal.SIGINT) try: time.sleep(10) except BaseException as exc: print("captured %r" % exc) ''') result = testdir.runpytest('-vv', '--assert=plain', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'test_cleanup_on_sigterm* 88% 19-20', '*1 passed*' ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32"', reason="fork not available on Windows") @pytest.mark.xfail('platform.python_implementation() == "PyPy"', reason="Interpreter seems buggy") def test_cleanup_on_sigterm_sig_ign(testdir): script = testdir.makepyfile(''' import os, signal, subprocess, sys, time def test_run(): proc = subprocess.Popen([sys.executable, __file__], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) time.sleep(1) proc.send_signal(signal.SIGINT) time.sleep(1) proc.terminate() stdout, stderr = proc.communicate() assert not stderr assert stdout == b"" # it appears signal handling is buggy on python 2? if sys.version_info == 3: assert proc.returncode in [128 + signal.SIGTERM, -signal.SIGTERM] if __name__ == "__main__": signal.signal(signal.SIGINT, signal.SIG_IGN) from pytest_cov.embed import cleanup_on_signal cleanup_on_signal(signal.SIGINT) try: time.sleep(10) except BaseException as exc: print("captured %r" % exc) ''') result = testdir.runpytest('-vv', '--assert=plain', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', 'test_cleanup_on_sigterm* 89% 23-24', '*1 passed*' ]) assert result.ret == 0 MODULE = ''' def func(): return 1 ''' CONFTEST = ''' import mod mod.func() ''' BASIC_TEST = ''' def test_basic(): x = True assert x ''' CONF_RESULT = 'mod* 2 * 100%*' def test_cover_conftest(testdir): testdir.makepyfile(mod=MODULE) testdir.makeconftest(CONFTEST) script = testdir.makepyfile(BASIC_TEST) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) assert result.ret == 0 result.stdout.fnmatch_lines([CONF_RESULT]) @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_cover_looponfail(testdir, monkeypatch): testdir.makepyfile(mod=MODULE) testdir.makeconftest(CONFTEST) script = testdir.makepyfile(BASIC_TEST) def mock_run(*args, **kwargs): return _TestProcess(*map(str, args)) monkeypatch.setattr(testdir, 'run', mock_run) assert testdir.run is mock_run if hasattr(testdir, '_pytester'): monkeypatch.setattr(testdir._pytester, 'run', mock_run) assert testdir._pytester.run is mock_run with testdir.runpytest('-v', f'--cov={script.dirpath()}', '--looponfail', script) as process: with dump_on_error(process.read): wait_for_strings( process.read, 30, # 30 seconds 'Stmts Miss Cover' ) @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_cover_conftest_dist(testdir): testdir.makepyfile(mod=MODULE) testdir.makeconftest(CONFTEST) script = testdir.makepyfile(BASIC_TEST) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-report=term-missing', '--dist=load', '--tx=2*popen', max_worker_restart_0, script) assert result.ret == 0 result.stdout.fnmatch_lines([CONF_RESULT]) def test_no_cover_marker(testdir): testdir.makepyfile(mod=MODULE) script = testdir.makepyfile(''' import pytest import mod import subprocess import sys @pytest.mark.no_cover def test_basic(): mod.func() subprocess.check_call([sys.executable, '-c', 'from mod import func; func()']) ''') result = testdir.runpytest('-v', '-ra', '--strict', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) assert result.ret == 0 result.stdout.fnmatch_lines(['mod* 2 * 1 * 50% * 2']) def test_no_cover_fixture(testdir): testdir.makepyfile(mod=MODULE) script = testdir.makepyfile(''' import mod import subprocess import sys def test_basic(no_cover): mod.func() subprocess.check_call([sys.executable, '-c', 'from mod import func; func()']) ''') result = testdir.runpytest('-v', '-ra', '--strict', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) assert result.ret == 0 result.stdout.fnmatch_lines(['mod* 2 * 1 * 50% * 2']) COVERAGERC = ''' [report] # Regexes for lines to exclude from consideration exclude_lines = raise NotImplementedError ''' EXCLUDED_TEST = ''' def func(): raise NotImplementedError def test_basic(): x = True assert x ''' EXCLUDED_RESULT = '4 * 100%*' def test_coveragerc(testdir): testdir.makefile('', coveragerc=COVERAGERC) script = testdir.makepyfile(EXCLUDED_TEST) result = testdir.runpytest('-v', '--cov-config=coveragerc', f'--cov={script.dirpath()}', '--cov-report=term-missing', script) assert result.ret == 0 result.stdout.fnmatch_lines([f'test_coveragerc* {EXCLUDED_RESULT}']) @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_coveragerc_dist(testdir): testdir.makefile('', coveragerc=COVERAGERC) script = testdir.makepyfile(EXCLUDED_TEST) result = testdir.runpytest('-v', '--cov-config=coveragerc', f'--cov={script.dirpath()}', '--cov-report=term-missing', '-n', '2', max_worker_restart_0, script) assert result.ret == 0 result.stdout.fnmatch_lines( [f'test_coveragerc_dist* {EXCLUDED_RESULT}']) SKIP_COVERED_COVERAGERC = ''' [report] skip_covered = True ''' SKIP_COVERED_TEST = ''' def func(): return "full coverage" def test_basic(): assert func() == "full coverage" ''' SKIP_COVERED_RESULT = '1 file skipped due to complete coverage.' @pytest.mark.parametrize('report_option', [ 'term-missing:skip-covered', 'term:skip-covered']) def test_skip_covered_cli(pytester, testdir, report_option): testdir.makefile('', coveragerc=SKIP_COVERED_COVERAGERC) script = testdir.makepyfile(SKIP_COVERED_TEST) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', f'--cov-report={report_option}', script) assert result.ret == 0 result.stdout.fnmatch_lines([SKIP_COVERED_RESULT]) def test_skip_covered_coveragerc_config(testdir): testdir.makefile('', coveragerc=SKIP_COVERED_COVERAGERC) script = testdir.makepyfile(SKIP_COVERED_TEST) result = testdir.runpytest('-v', '--cov-config=coveragerc', f'--cov={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', f'--cov={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('tuple(map(int, xdist.__version__.split("."))) >= (3, 0, 2)', reason="--boxed option was removed in version 3.0.2") @pytest.mark.skipif('sys.platform == "win32"') def test_dist_boxed(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--assert=plain', f'--cov={script.dirpath()}', '--boxed', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_dist_boxed* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*' ]) assert result.ret == 0 @pytest.mark.skipif('sys.platform == "win32"') @pytest.mark.skipif('sys.version_info[0] > 2 and platform.python_implementation() == "PyPy"', reason="strange optimization on PyPy3") def test_dist_bare_cov(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--cov', '-n', '1', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_dist_bare_cov* {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', f'--cov={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', f'--cov={script.dirpath()}', '--cov-report=', script) stdout = result.stdout.str() # We don't want the path to the executable to fail the test if we happen # to put the project in a directory with "coverage" in it. stdout = stdout.replace(sys.executable, "") assert 'coverage' not in stdout assert result.ret == 0 def test_coverage_file(testdir): script = testdir.makepyfile(SCRIPT) data_file_name = 'covdata' os.environ['COVERAGE_FILE'] = data_file_name try: result = testdir.runpytest('-v', f'--cov={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', f'--cov={script.dirpath()}', script) assert result.ret == 0 assert glob.glob(str(testdir.tmpdir.join('some/special/place/coverage-data*'))) @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_external_data_file_xdist(testdir): script = testdir.makepyfile(SCRIPT) testdir.tmpdir.join('.coveragerc').write(""" [run] parallel = true data_file = %s """ % testdir.tmpdir.join('some/special/place/coverage-data').ensure()) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '-n', '1', max_worker_restart_0, script) assert result.ret == 0 assert glob.glob(str(testdir.tmpdir.join('some/special/place/coverage-data*'))) @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_xdist_no_data_collected(testdir): testdir.makepyfile(target="x = 123") script = testdir.makepyfile(""" import target def test_foobar(): assert target.x == 123 """) result = testdir.runpytest('-v', '--cov=target', '-n', '1', script) assert 'no-data-collected' not in result.stderr.str() assert 'no-data-collected' not in result.stdout.str() assert 'module-not-imported' not in result.stderr.str() assert 'module-not-imported' not in result.stdout.str() assert result.ret == 0 def test_external_data_file_negative(testdir): script = testdir.makepyfile(SCRIPT) testdir.tmpdir.join('.coveragerc').write("") result = testdir.runpytest('-v', f'--cov={script.dirpath()}', script) assert result.ret == 0 assert glob.glob(str(testdir.tmpdir.join('.coverage*'))) @xdist_params def test_append_coverage(pytester, testdir, opts, prop): script = testdir.makepyfile(test_1=prop.code) testdir.tmpdir.join('.coveragerc').write(prop.fullconf) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', script, *opts.split() + prop.args) result.stdout.fnmatch_lines([ f'test_1* {prop.result}*', ]) script2 = testdir.makepyfile(test_2=prop.code2) result = testdir.runpytest('-v', '--cov-append', f'--cov={script2.dirpath()}', script2, *opts.split() + prop.args) result.stdout.fnmatch_lines([ f'test_1* {prop.result}*', f'test_2* {prop.result2}*', ]) @xdist_params def test_do_not_append_coverage(pytester, testdir, opts, prop): script = testdir.makepyfile(test_1=prop.code) testdir.tmpdir.join('.coveragerc').write(prop.fullconf) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', script, *opts.split()+prop.args) result.stdout.fnmatch_lines([ f'test_1* {prop.result}*', ]) script2 = testdir.makepyfile(test_2=prop.code2) result = testdir.runpytest('-v', f'--cov={script2.dirpath()}', script2, *opts.split()+prop.args) result.stdout.fnmatch_lines([ 'test_1* 0%', f'test_2* {prop.result2}*', ]) @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_append_coverage_subprocess(testdir): scripts = testdir.makepyfile(parent_script=SCRIPT_PARENT, child_script=SCRIPT_CHILD) parent_script = scripts.dirpath().join('parent_script.py') result = testdir.runpytest('-v', f'--cov={scripts.dirpath()}', '--cov-append', '--cov-report=term-missing', '--dist=load', '--tx=2*popen', max_worker_restart_0, parent_script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'child_script* {CHILD_SCRIPT_RESULT}*', f'parent_script* {PARENT_SCRIPT_RESULT}*', ]) assert result.ret == 0 def test_pth_failure(monkeypatch): with open('src/pytest-cov.pth') as fh: payload = fh.read() class SpecificError(Exception): pass def bad_init(): raise SpecificError() buff = StringIO() from pytest_cov import embed monkeypatch.setattr(embed, 'init', bad_init) monkeypatch.setattr(sys, 'stderr', buff) monkeypatch.setitem(os.environ, 'COV_CORE_SOURCE', 'foobar') exec(payload) expected = ( "pytest-cov: Failed to setup subprocess coverage. " "Environ: {'COV_CORE_SOURCE': 'foobar'} Exception: SpecificError()\n" ) assert buff.getvalue() == expected def test_double_cov(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--assert=plain', '--cov', f'--cov={script.dirpath()}', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_double_cov* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*' ]) assert result.ret == 0 def test_double_cov2(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--assert=plain', '--cov', '--cov', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_double_cov2* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*' ]) assert result.ret == 0 def test_cov_reset(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--assert=plain', f'--cov={script.dirpath()}', '--cov-reset', script) assert 'coverage: platform' not in result.stdout.str() def test_cov_reset_then_set(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--assert=plain', f'--cov={script.dirpath()}', '--cov-reset', f'--cov={script.dirpath()}', script) result.stdout.fnmatch_lines([ '*- coverage: platform *, python * -*', f'test_cov_reset_then_set* {SCRIPT_SIMPLE_RESULT}*', '*1 passed*' ]) @pytest.mark.skipif('sys.platform == "win32" and platform.python_implementation() == "PyPy"') def test_cov_and_no_cov(testdir): script = testdir.makepyfile(SCRIPT_SIMPLE) result = testdir.runpytest('-v', '--cov', '--no-cov', '-n', '1', '-s', script) assert 'Coverage disabled via --no-cov switch!' not in result.stdout.str() assert 'Coverage disabled via --no-cov switch!' not in result.stderr.str() assert result.ret == 0 def find_labels(text, pattern): all_labels = collections.defaultdict(set) lines = text.splitlines() for lineno, line in enumerate(lines, start=1): labels = re.findall(pattern, line) for label in labels: all_labels[label].add(lineno) return all_labels # The contexts and their labels in contextful.py EXPECTED_CONTEXTS = { '': 'c0', 'test_contexts.py::test_01|run': 'r1', 'test_contexts.py::test_02|run': 'r2', 'test_contexts.py::OldStyleTests::test_03|setup': 's3', 'test_contexts.py::OldStyleTests::test_03|run': 'r3', 'test_contexts.py::OldStyleTests::test_04|run': 'r4', 'test_contexts.py::OldStyleTests::test_04|teardown': 't4', 'test_contexts.py::test_05|setup': 's5', 'test_contexts.py::test_05|run': 'r5', 'test_contexts.py::test_06|setup': 's6', 'test_contexts.py::test_06|run': 'r6', 'test_contexts.py::test_07|setup': 's7', 'test_contexts.py::test_07|run': 'r7', 'test_contexts.py::test_08|run': 'r8', 'test_contexts.py::test_09[1]|setup': 's9-1', 'test_contexts.py::test_09[1]|run': 'r9-1', 'test_contexts.py::test_09[2]|setup': 's9-2', 'test_contexts.py::test_09[2]|run': 'r9-2', 'test_contexts.py::test_09[3]|setup': 's9-3', 'test_contexts.py::test_09[3]|run': 'r9-3', 'test_contexts.py::test_10|run': 'r10', 'test_contexts.py::test_11[1-101]|run': 'r11-1', 'test_contexts.py::test_11[2-202]|run': 'r11-2', 'test_contexts.py::test_12[one]|run': 'r12-1', 'test_contexts.py::test_12[two]|run': 'r12-2', 'test_contexts.py::test_13[3-1]|run': 'r13-1', 'test_contexts.py::test_13[3-2]|run': 'r13-2', 'test_contexts.py::test_13[4-1]|run': 'r13-3', 'test_contexts.py::test_13[4-2]|run': 'r13-4', } @pytest.mark.skipif("coverage.version_info < (5, 0)") @pytest.mark.skipif("coverage.version_info > (6, 4)") @xdist_params def test_contexts(pytester, testdir, opts): with open(os.path.join(os.path.dirname(__file__), "contextful.py")) as f: contextful_tests = f.read() script = testdir.makepyfile(contextful_tests) result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-context=test', script, *opts.split() ) assert result.ret == 0 result.stdout.fnmatch_lines([ 'test_contexts* 100%*', ]) data = coverage.CoverageData(".coverage") data.read() assert data.measured_contexts() == set(EXPECTED_CONTEXTS) measured = data.measured_files() assert len(measured) == 1 test_context_path = list(measured)[0] assert test_context_path.lower() == os.path.abspath("test_contexts.py").lower() line_data = find_labels(contextful_tests, r"[crst]\d+(?:-\d+)?") for context, label in EXPECTED_CONTEXTS.items(): if context == '': continue data.set_query_context(context) actual = set(data.lines(test_context_path)) assert line_data[label] == actual, f"Wrong lines for context {context!r}" @pytest.mark.skipif("coverage.version_info >= (5, 0)") def test_contexts_not_supported(testdir): script = testdir.makepyfile("a = 1") result = testdir.runpytest('-v', f'--cov={script.dirpath()}', '--cov-context=test', script, ) result.stderr.fnmatch_lines([ '*argument --cov-context: Contexts are only supported with coverage.py >= 5.x', ]) assert result.ret != 0 def test_issue_417(testdir): # https://github.com/pytest-dev/pytest-cov/issues/417 whatever = testdir.maketxtfile(whatever="") testdir.inline_genitems(whatever) python-pytest-cov-4.1.0/tox.ini000066400000000000000000000062721444602742700165040ustar00rootroot00000000000000[testenv:bootstrap] deps = jinja2 tox skip_install = true commands = python ci/bootstrap.py --no-env passenv = * ; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist [tox] envlist = check py{37,38,39,310,311,py37,py38,py39}-pytest{73}-xdist330-coverage{72} docs [testenv] extras = testing setenv = PYTHONUNBUFFERED=yes # Use env vars for (optional) pinning of deps. pytest46: _DEP_PYTEST=pytest==4.6.10 pytest53: _DEP_PYTEST=pytest==5.3.2 pytest54: _DEP_PYTEST=pytest==5.4.3 pytest60: _DEP_PYTEST=pytest==6.0.2 pytest61: _DEP_PYTEST=pytest==6.1.2 pytest62: _DEP_PYTEST=pytest==6.2.5 pytest70: _DEP_PYTEST=pytest==7.0.1 pytest71: _DEP_PYTEST=pytest==7.1.2 pytest72: _DEP_PYTEST=pytest==7.2.0 pytest73: _DEP_PYTEST=pytest==7.3.1 xdist127: _DEP_PYTESTXDIST=pytest-xdist==1.27.0 xdist129: _DEP_PYTESTXDIST=pytest-xdist==1.29.0 xdist131: _DEP_PYTESTXDIST=pytest-xdist==1.31.0 xdist132: _DEP_PYTESTXDIST=pytest-xdist==1.32.0 xdist133: _DEP_PYTESTXDIST=pytest-xdist==1.33.0 xdist134: _DEP_PYTESTXDIST=pytest-xdist==1.34.0 xdist200: _DEP_PYTESTXDIST=pytest-xdist==2.0.0 xdist201: _DEP_PYTESTXDIST=pytest-xdist==2.1.0 xdist202: _DEP_PYTESTXDIST=pytest-xdist==2.2.0 xdist250: _DEP_PYTESTXDIST=pytest-xdist==2.5.0 xdist320: _DEP_PYTESTXDIST=pytest-xdist==3.2.0 xdist330: _DEP_PYTESTXDIST=pytest-xdist==3.3.1 xdistdev: _DEP_PYTESTXDIST=git+https://github.com/pytest-dev/pytest-xdist.git#egg=pytest-xdist coverage45: _DEP_COVERAGE=coverage==4.5.4 coverage50: _DEP_COVERAGE=coverage==5.0.4 coverage51: _DEP_COVERAGE=coverage==5.1 coverage52: _DEP_COVERAGE=coverage==5.2.1 coverage53: _DEP_COVERAGE=coverage==5.3.1 coverage54: _DEP_COVERAGE=coverage==5.4 coverage55: _DEP_COVERAGE=coverage==5.5 coverage60: _DEP_COVERAGE=coverage==6.0.2 coverage61: _DEP_COVERAGE=coverage==6.1.2 coverage62: _DEP_COVERAGE=coverage==6.2 coverage63: _DEP_COVERAGE=coverage==6.3.3 coverage64: _DEP_COVERAGE=coverage==6.4.2 coverage65: _DEP_COVERAGE=coverage==6.5.0 coverage72: _DEP_COVERAGE=coverage==7.2.5 # For testing against a coverage.py working tree. coveragedev: _DEP_COVERAGE=-e{env:COVERAGE_HOME} passenv = * deps = {env:_DEP_PYTEST:pytest} {env:_DEP_PYTESTXDIST:pytest-xdist} {env:_DEP_COVERAGE:coverage} pip_pre = true commands = {posargs:pytest -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 = check-manifest colorama # TODO Remove when isort > v6.0.0b2 is released. docutils flake8 isort pygments readme-renderer skip_install = true usedevelop = false commands = python setup.py check --strict --metadata --restructuredtext check-manifest {toxinidir} flake8 src tests setup.py isort --check-only --diff src tests setup.py