pax_global_header00006660000000000000000000000064144011432620014507gustar00rootroot0000000000000052 comment=007f5aff5d1c817883385a5f61f742dd11776dc6 covdefaults-2.3.0/000077500000000000000000000000001440114326200140305ustar00rootroot00000000000000covdefaults-2.3.0/.github/000077500000000000000000000000001440114326200153705ustar00rootroot00000000000000covdefaults-2.3.0/.github/workflows/000077500000000000000000000000001440114326200174255ustar00rootroot00000000000000covdefaults-2.3.0/.github/workflows/main.yml000066400000000000000000000003301440114326200210700ustar00rootroot00000000000000name: main on: push: branches: [main, test-me-*] tags: pull_request: jobs: main: uses: asottile/workflows/.github/workflows/tox.yml@v1.0.0 with: env: '["py37", "py38", "py39", "py310"]' covdefaults-2.3.0/.gitignore000066400000000000000000000000421440114326200160140ustar00rootroot00000000000000*.egg-info *.pyc /.coverage /.tox covdefaults-2.3.0/.pre-commit-config.yaml000066400000000000000000000022231440114326200203100ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: debug-statements - id: double-quote-string-fixer - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt rev: v2.2.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder_python_imports rev: v3.9.0 hooks: - id: reorder-python-imports args: [--py37-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma rev: v2.4.0 hooks: - id: add-trailing-comma args: [--py36-plus] - repo: https://github.com/asottile/pyupgrade rev: v3.3.1 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/pre-commit/mirrors-autopep8 rev: v2.0.1 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.0.1 hooks: - id: mypy covdefaults-2.3.0/LICENSE000066400000000000000000000020431440114326200150340ustar00rootroot00000000000000Copyright (c) 2020 Anthony Sottile 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. covdefaults-2.3.0/README.md000066400000000000000000000101601440114326200153050ustar00rootroot00000000000000[![build status](https://github.com/asottile/covdefaults/actions/workflows/main.yml/badge.svg)](https://github.com/asottile/covdefaults/actions/workflows/main.yml) [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/asottile/covdefaults/main.svg)](https://results.pre-commit.ci/latest/github/asottile/covdefaults/main) covdefaults =========== A coverage plugin to provide sensible default settings ## installation ```bash pip install covdefaults ``` ## usage to enable the plugin, add `covdefaults` to your coverage plugins in `.coveragerc`: ```ini [run] plugins = covdefaults ``` in `setup.cfg` / `tox.ini`: ```ini [coverage:run] plugins = covdefaults ``` in `pyproject.toml`: ```ini [tool.coverage.run] plugins = ["covdefaults"] ``` ## default settings ### `[coverage:run]` ```ini branch = True source = . omit = */__main__.py */setup.py ``` ### `[coverage:report]` ```ini show_missing = True skip_covered = True fail_under = 100 exclude_lines = # a more strict default pragma \# pragma: no cover\b # allow defensive code ^\s*raise AssertionError\b ^\s*raise NotImplementedError\b ^\s*return NotImplemented\b ^\s*raise$ # typing-related code ^\s*if (False|TYPE_CHECKING): : \.\.\.(\s*#.*)?$ ^ +\.\.\.$ -> ['"]?NoReturn['"]?: # non-runnable code if __name__ == ['"]__main__['"]:$ # additional platform related pragmas (see below) # additional version related pragmas (see below) partial_branches = # a more strict default pragma \# pragma: no cover\b # out platform pragmas \# pragma: (nt|posix|cygwin|darwin|linux|msys|win32|cpython|pypy) (no )?cover\b # our version pragmas \# pragma: (>=?|<=?|==|!=)\d+\.\d+ cover\b ``` ### platform specific `# pragma: no cover` several `# pragma: no cover` tags will be added automatically based on the platform and implementation. these will be in the form of: ```python # pragma: TAG no cover ``` or ```python # pragma: TAG cover ``` these tags will be generated by the following values: - `os.name` - `nt` (windows) - `posix` (linux, macOs, cygwin, etc.) - `sys.platform` - `cygwin` - `darwin` (macOs) - `linux` - `msys` - `win32` - `sys.implementation.name` - `cpython` - `pypy` for every tag which does not match, you can use negation. here's an example: ```python if sys.platform == 'win32': # pragma: win32 cover bin_dir = 'Scripts' else: # pragma: win32 no cover bin_dir = 'bin' ``` note here that `# pragma: win32 cover` will become a "no cover" for everything which is not `win32` -- whereas the `# pragma: win32 no cover` will be a "no cover" only on `win32`. ### version specific `# pragma: no cover` several `# pragma: no cover` tags will be added automatically based on the platform and implementation. these will be in the form of: ```python # pragma: >=#.# cover ``` where the comparison operator is one of `>`, `>=`, `<`, `<=`, `==`, `!=` for example: ```python if sys.version_info >= (3, 9): # pragma: >=3.9 cover print('3.9+') else: # pragma: <3.9 cover print('old') ``` ### overriding options several of the options can be overridden / extended in your coverage configuration. the examples below assume `.coveragerc` however any of the files `coverage` supports work as well. #### `run:omit` ```ini [run] omit = pre_commit/resources/* ``` this will result in the `pre_commit/resources/*` being `omit`ted in addition to the defaults provided by `covdefaults`. ```ini [covdefaults] subtract_omit = */__main__.py ``` this will result in `*/__main__.py` not being `omit`ted (`*/__main__.py` is among the defaults provided by `covdefaults`). #### `run:source` ```ini [run] source = $PWD ``` covdefaults will not override this value to `.` if it is set manually. #### `report:exclude_lines` ```ini [report] exclude_lines = ^if MYPY:$ ``` this will result in lines matching `^if MYPY:$` to additionally be excluded from coverage in addition to the defaults provided by `covdefaults`. #### `report:fail_under` ```ini [report] fail_under = 90 ``` `covdefaults` will not change the value if you provide one for `fail_under` covdefaults-2.3.0/covdefaults.py000066400000000000000000000105071440114326200167240ustar00rootroot00000000000000from __future__ import annotations import os import sys from typing import Any from coverage import CoveragePlugin from coverage.config import CoverageConfig from coverage.config import DEFAULT_EXCLUDE from coverage.plugin_support import Plugins _ALL = ( # os.name 'nt', 'posix', # sys.platform 'cygwin', 'darwin', 'linux', 'msys', 'win32', # sys.implementation.name 'cpython', 'pypy', ) def _plat_impl_pragmas() -> list[str]: tags = {os.name, sys.platform, sys.implementation.name} ret = [fr'# pragma: {tag} cover\b' for tag in _ALL if tag not in tags] ret.extend(fr'# pragma: {tag} no cover\b' for tag in tags) return ret def _lt(n: int) -> str: n_s = str(n) digit = r'\d' parts = [ f'{n_s[:i]}[0-{int(n_s[i]) - 1}]{len(n_s[i + 1:]) * digit}' for i in range(len(n_s)) if n_s[i] != '0' ] if len(n_s) > 1: parts.append(f'{digit}{{1,{len(n_s) - 1}}}') return f'({"|".join(parts)})' def _gt(n: int) -> str: n_s = str(n) digit = r'\d' parts = [ f'{n_s[:i]}[{int(n_s[i]) + 1}-9]{len(n_s[i + 1:]) * digit}' for i in range(len(n_s)) if n_s[i] != '9' ] parts.append(f'{digit}{{{len(n_s) + 1},}}') return f'({"|".join(parts)})' def _version_pragmas( major: int = sys.version_info[0], minor: int = sys.version_info[1], ) -> list[str]: return [ # < fr'# pragma: <=?{_lt(major)}\.\d+ cover\b', fr'# pragma: <=?{major}\.{_lt(minor)} cover\b', fr'# pragma: <{major}\.{minor} cover\b', # > fr'# pragma: >=?{_gt(major)}\.\d+ cover\b', fr'# pragma: >=?{major}\.{_gt(minor)} cover\b', fr'# pragma: >{major}\.{minor} cover\b', # != / == fr'# pragma: !={major}\.{minor} cover\b', fr'# pragma: ==(?!{major}\.{minor})\d+\.\d+ cover\b', ] OPTIONS: tuple[tuple[str, Any], ...] = ( ('run:branch', True), ('report:show_missing', True), ('report:skip_covered', True), ) EXTEND = ( ('run:omit', ['*/__main__.py', '*/setup.py']), ( 'report:exclude_lines', [ # a more strict default pragma r'# pragma: no cover\b', # allow defensive code r'^\s*raise AssertionError\b', r'^\s*raise NotImplementedError\b', r'^\s*return NotImplemented\b', r'^\s*raise$', # typing-related code r'^\s*if (False|TYPE_CHECKING):', r': \.\.\.(\s*#.*)?$', r'^ +\.\.\.$', r'-> [\'"]?NoReturn[\'"]?:', r'^\s*assert_never\b', # non-runnable code r'^if __name__ == [\'"]__main__[\'"]:$', *_plat_impl_pragmas(), *_version_pragmas(), ], ), ( 'report:partial_branches', [ r'# pragma: no branch\b', # platform specific no cover fr'# pragma: ({"|".join(_ALL)}) (no )?cover\b', # version specific no cover r'# pragma: (>=?|<=?|==|!=)\d+\.\d+ cover\b', ], ), ) class CovDefaults(CoveragePlugin): def __init__(self, subtract_omit: str = '') -> None: self._subtract_omit = subtract_omit.split() def configure(self, config: CoverageConfig) -> None: for k, v in OPTIONS: config.set_option(k, v) if config.get_option('run:source') is None: config.set_option('run:source', ['.']) for k, v in EXTEND: before = set(config.get_option(k) or ()) before.update(v) config.set_option(k, sorted(before)) # subtract omit settings if requested if self._subtract_omit: omit = set(config.get_option('run:omit')) omit.difference_update(self._subtract_omit) config.set_option('run:omit', sorted(omit)) # remove DEFAULT_EXCLUDE, we add a more-strict casing exclude = set(config.get_option('report:exclude_lines')) exclude.difference_update(DEFAULT_EXCLUDE) config.set_option('report:exclude_lines', sorted(exclude)) # fail_under: if they specify a value then honor it if not config.get_option('report:fail_under'): config.set_option('report:fail_under', 100) def coverage_init(reg: Plugins, options: dict[str, str]) -> None: reg.add_configurer(CovDefaults(**options)) covdefaults-2.3.0/requirements-dev.txt000066400000000000000000000000201440114326200200600ustar00rootroot00000000000000coverage pytest covdefaults-2.3.0/setup.cfg000066400000000000000000000020321440114326200156460ustar00rootroot00000000000000[metadata] name = covdefaults version = 2.3.0 description = A coverage plugin to provide sensible default settings long_description = file: README.md long_description_content_type = text/markdown url = https://github.com/asottile/covdefaults author = Anthony Sottile author_email = asottile@umich.edu license = MIT license_file = LICENSE classifiers = License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy [options] py_modules = covdefaults install_requires = coverage>=6.0.2 python_requires = >=3.7 [bdist_wheel] universal = True [coverage:run] plugins = covdefaults [mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_defs = true warn_redundant_casts = true warn_unused_ignores = true [mypy-testing.*] disallow_untyped_defs = false [mypy-tests.*] disallow_untyped_defs = false covdefaults-2.3.0/setup.py000066400000000000000000000001111440114326200155330ustar00rootroot00000000000000from __future__ import annotations from setuptools import setup setup() covdefaults-2.3.0/tests/000077500000000000000000000000001440114326200151725ustar00rootroot00000000000000covdefaults-2.3.0/tests/__init__.py000066400000000000000000000000001440114326200172710ustar00rootroot00000000000000covdefaults-2.3.0/tests/covdefaults_test.py000066400000000000000000000164511440114326200211310ustar00rootroot00000000000000from __future__ import annotations import importlib import re import pytest from coverage.config import CoverageConfig from coverage.config import DEFAULT_EXCLUDE from coverage.plugin_support import Plugins import covdefaults configure = covdefaults.CovDefaults().configure def test_plat_impl_pragmas(): pragmas = covdefaults._plat_impl_pragmas() assert {p.split()[2] for p in pragmas} == set(covdefaults._ALL) # other pragmas for s in pragmas[:-3]: c, pragma, _, cover = s.split() assert (c, pragma, cover) == ('#', 'pragma:', r'cover\b'), s # self pragmas for s in pragmas[-3:]: c, pragma, _, no, cover = s.split() assert (c, pragma, no, cover) == ('#', 'pragma:', 'no', r'cover\b'), s def _matches_version_pragma(major, minor, s): regexes = covdefaults._version_pragmas(major, minor) return any(re.match(reg, s) for reg in regexes) @pytest.mark.parametrize( ('s', 'expected'), ( # < ('# pragma: <2.7 cover', True), ('# pragma: <3.6 cover', True), ('# pragma: <3.7 cover', True), ('# pragma: <3.8 cover', False), ('# pragma: <3.10 cover', False), # <= ('# pragma: <=2.7 cover', True), ('# pragma: <=3.6 cover', True), ('# pragma: <=3.7 cover', False), ('# pragma: <=3.8 cover', False), ('# pragma: <=3.10 cover', False), # > ('# pragma: >2.7 cover', False), ('# pragma: >3.6 cover', False), ('# pragma: >3.7 cover', True), ('# pragma: >3.8 cover', True), ('# pragma: >3.10 cover', True), # >= ('# pragma: >=2.7 cover', False), ('# pragma: >=3.6 cover', False), ('# pragma: >=3.7 cover', False), ('# pragma: >=3.8 cover', True), ('# pragma: >=3.10 cover', True), # == ('# pragma: ==3.6 cover', True), ('# pragma: ==3.7 cover', False), ('# pragma: ==3.8 cover', True), # != ('# pragma: !=3.6 cover', False), ('# pragma: !=3.7 cover', True), ('# pragma: !=3.8 cover', False), ), ) def test_version_pragmas_py37(s, expected): assert _matches_version_pragma(3, 7, s) == expected @pytest.mark.parametrize( ('s', 'expected'), ( # < ('# pragma: <2.7 cover', True), ('# pragma: <3.9 cover', True), ('# pragma: <3.10 cover', True), ('# pragma: <3.11 cover', False), # <= ('# pragma: <=2.7 cover', True), ('# pragma: <=3.9 cover', True), ('# pragma: <=3.10 cover', False), ('# pragma: <=3.11 cover', False), # > ('# pragma: >2.7 cover', False), ('# pragma: >3.9 cover', False), ('# pragma: >3.10 cover', True), ('# pragma: >3.11 cover', True), # >= ('# pragma: >=2.7 cover', False), ('# pragma: >=3.9 cover', False), ('# pragma: >=3.10 cover', False), ('# pragma: >=3.11 cover', True), # == ('# pragma: ==3.9 cover', True), ('# pragma: ==3.10 cover', False), ('# pragma: ==3.11 cover', True), # != ('# pragma: !=3.9 cover', False), ('# pragma: !=3.10 cover', True), ('# pragma: !=3.11 cover', False), ), ) def test_version_pragmas_py310(s, expected): assert _matches_version_pragma(3, 10, s) == expected @pytest.fixture def configured(): cfg = CoverageConfig() configure(cfg) return cfg def test_constant_options(configured): assert configured.get_option('run:branch') is True assert configured.get_option('run:source') == ['.'] assert configured.get_option('report:show_missing') is True assert configured.get_option('report:skip_covered') is True assert configured.get_option('report:fail_under') == 100 def test_source_already_set(): cfg = CoverageConfig() cfg.set_option('run:source', ['/tmp/foo']) configure(cfg) assert cfg.get_option('run:source') == ['/tmp/foo'] def test_extends_existing_omit(): cfg = CoverageConfig() cfg.set_option('run:omit', ['pre_commit/resources/*']) configure(cfg) assert cfg.get_option('run:omit') == [ '*/__main__.py', '*/setup.py', 'pre_commit/resources/*', ] def test_subtract_omit(): cfg = CoverageConfig() covdefaults.CovDefaults(subtract_omit='*/__main__.py').configure(cfg) assert cfg.get_option('run:omit') == [ '*/setup.py', ] def test_exclude_lines_does_not_include_defaults(configured): ret = set(configured.get_option('report:exclude_lines')) assert set(DEFAULT_EXCLUDE) & ret == set() @pytest.mark.parametrize( 'src', ( 'if x: # pragma: no cover\n', 'if x: # pragma: no cover (py38+)\n', 'if x: # noqa # pragma: no cover\n', 'if x: # pragma: no cover # noqa\n', 'raise AssertionError("unreachable!")\n', 'raise NotImplementedError("TODO!")\n', ' return NotImplemented\n', ' raise\n', 'if False:\n', ' if False:\n', 'if TYPE_CHECKING:\n', ' if TYPE_CHECKING:\n', 'assert_never(instance)', 'def f(x: int) -> int: ...\n', 'def f(x: int) -> int:\n ...\n', 'def f(x: int) -> C: ...# noqa: F821\n', 'def f(x: int) -> C: ... # noqa: F821\n', 'def never_returns() -> NoReturn:\n', 'def never_returns() -> "NoReturn":\n', "def never_returns() -> 'NoReturn':\n", 'if __name__ == "__main__":\n', "if __name__ == '__main__':\n", ), ) def test_excludes_lines(configured, src): for reg in configured.get_option('report:exclude_lines'): if any(re.search(reg, line) for line in src.splitlines()): break else: raise AssertionError(f'no regex matched {src!r}') @pytest.mark.parametrize( 'src', ( 'if True: # pragma: no branch\n', 'if sys.platform == "win32": # pragma: win32 cover\n', 'if sys.platform != "win32": # pragma: win32 no cover\n', 'if sys.version_info >= (3, 9): # pragma: >=3.9 cover\n', 'if sys.version_info > (3, 9): # pragma: >3.9 cover\n', 'if sys.version_info <= (3, 9): # pragma: <=3.9 cover\n', 'if sys.version_info < (3, 9): # pragma: <3.9 cover\n', 'if sys.version_info == (3, 9): # pragma: ==3.9 cover\n', 'if sys.version_info != (3, 9): # pragma: !=3.9 cover\n', ), ) def test_partial_branches(configured, src): for reg in configured.get_option('report:partial_branches'): if any(re.search(reg, line) for line in src.splitlines()): break else: raise AssertionError(f'no regex matched {src!r}') def test_extends_existing_exclude_lines(): cfg = CoverageConfig() cfg.set_option('report:exclude_lines', ['^if MYPY:$']) configure(cfg) assert '^if MYPY:$' in cfg.get_option('report:exclude_lines') def test_configure_keeps_existing_fail_under(): cfg = CoverageConfig() cfg.set_option('report:fail_under', 42) configure(cfg) assert cfg.get_option('report:fail_under') == 42 def test_coverage_init(): cfg = CoverageConfig() plugin_manager = Plugins.load_plugins(['covdefaults'], cfg) assert plugin_manager.get('covdefaults.CovDefaults') def test_fix_coverage(): """since we get imported as a coverage plugin -- need to re-scan module""" importlib.reload(covdefaults) covdefaults-2.3.0/tox.ini000066400000000000000000000005121440114326200153410ustar00rootroot00000000000000[tox] envlist = py37,py38,pypy3,pre-commit [testenv] deps = -rrequirements-dev.txt commands = coverage erase coverage run -m pytest {posargs:tests} coverage report [testenv:pre-commit] skip_install = true deps = pre-commit commands = pre-commit run --all-files --show-diff-on-failure [pep8] ignore = E265,E501,W504