File: covdefaults.py

package info (click to toggle)
python-covdefaults 2.3.0-3
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 140 kB
  • sloc: python: 303; makefile: 2
file content (151 lines) | stat: -rw-r--r-- 4,423 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
from __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))