FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Feature request: exclude coverage collected while executing specific tests · Issue #2178 · coveragepy/coveragepy · GitHub

Feature request: exclude coverage collected while executing specific tests #2178

Description

Hi! First of all, thank you for coverage.py. It is one of those tools that quietly makes an enormous amount of Python work possible.

I ran into a case where I would like coverage.py to support excluding coverage data based on where execution came from, not only based on which measured file was executed.

Problem

In one of my projects, I have normal runtime tests and separate typing tests:

tests/documentation/
tests/units/
tests/typing/

The typing tests are still real tests. They are collected and run by pytest, and they must be able to fail CI if the public typing contract regresses. But they are not intended to count toward runtime branch/line coverage.

The current CI command is intentionally a single pytest run under coverage:

coverage run -m pytest -n auto --cache-clear --assert=plain
coverage combine
coverage report -m --fail-under=100 --omit='*tests*'
coverage xml --omit='*tests*'

The project also uses:

[tool.coverage.run]
branch = true
parallel = true
source = ["suby"]

The issue is that tests/typing imports and exercises public APIs from suby, so those typing tests can execute lines in measured source files. --omit='*tests*' does not help, because the measured files are not test files; they are the package files. The question is not "should tests/typing be included as measured files?", but rather "should execution caused by tests/typing be allowed to add coverage for suby?".

In my case the answer is no: typing tests should run and fail normally, but they should contribute zero coverage.

Why the existing features do not quite fit

As far as I can tell, the current mechanisms are file-oriented or context-reporting-oriented:

  • [run] source, [run] omit, and report/XML --omit filter measured files. They cannot say "ignore coverage of suby/run.py when it was executed from tests/typing."
  • [run] dynamic_context = "test_function" is useful for answering "which test covered this line?", but it does not solve this case by itself. Some coverage can happen during pytest collection or module import before a test-function context is active, so it is recorded in the empty context.
  • Context filtering in reports is inclusion-based, and coverage xml does not expose a CLI --contexts option. Even if it did, this would still require every relevant phase, including collection/import, to have a reliable non-typing context.
  • pytest-cov --cov-context can attach per-test contexts, but this project currently uses coverage run -m pytest directly, partly because it also has subprocess coverage startup behavior. More importantly, per-test contexts still do not naturally cover import/collection-time execution from a particular test path.

Ideal solution

It would be very useful if coverage.py had a native way to exclude measurements based on execution origin, for example by caller stack file path or by an explicitly excluded dynamic context.

I imagine something like one of these designs, though I am not attached to the exact names:

[tool.coverage.run]
source = ["suby"]
branch = true

# Do not record coverage for measured files while execution is happening
# under frames whose file path matches one of these patterns.
omit_when_stack_matches = [
    "*/tests/typing/*",
]

or:

[tool.coverage.run]
dynamic_context = "test_function"

[tool.coverage.report]
exclude_contexts = [
    "tests.typing.*",
]

[tool.coverage.xml]
exclude_contexts = [
    "tests.typing.*",
]

The first form would be especially helpful because it does not require the test runner to set perfect contexts for collection/import-time work. Conceptually, it would be the execution-origin counterpart to omit: keep measuring suby, but do not record data for suby while the active call stack is rooted in tests/typing.

I realize stack-based filtering might have performance or implementation tradeoffs. A lower-level public API could also help, for example a documented, nesting-safe way for test runners/plugins to suspend and resume measurement around selected collection or runtest phases:

with coverage.current().paused():
    ...

Coverage.stop() and Coverage.start() exist today, but using them inside pytest hooks feels like relying on a fairly delicate interaction with the active collector. A purpose-built pause/resume API would make this kind of integration less surprising.

Workarounds I am considering today

Without a native feature, I have to choose between a few less-than-ideal options:

  1. Split the CI into two commands:

    coverage run -m pytest tests/documentation tests/units ...
    pytest tests/typing ...

    This is simple, but it changes the test execution shape. I would prefer to keep one pytest run so typing tests are not treated as a separate, easier-to-forget command.

  2. Add pytest hook instrumentation that detects paths under tests/typing and temporarily stops/restarts coverage during collection and runtest phases.

    This keeps one command and preserves test failures, but it is fairly invasive test-runner plumbing for what feels like a coverage policy.

  3. Post-process the .coverage SQLite data or generate XML through a custom script.

    This feels fragile, and it still depends on having reliable contexts for import/collection-time execution.

What I would like to be able to guarantee

I would like to be able to prove all of these at the same time:

  • tests under tests/typing are collected and executed;
  • if a typing test fails, the same CI test command fails;
  • code executed only from tests/typing does not appear as covered in text, XML, JSON, or uploaded coverage reports;
  • normal runtime tests still determine the coverage percentage and --fail-under result.

Would a native "omit by execution origin" or "exclude contexts from all report formats" feature fit coverage.py's model? If there is already a better-supported way to express this, I would be happy to use it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


    Back | FazBrowse Home | New Git URL