• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

psf / black / 6915920786

18 Nov 2023 07:42PM UTC coverage: 96.375%. Remained the same
6915920786

push

github

web-flow
Upgrade mypy to 1.6.1 (#4049)

2950 of 3159 branches covered (0.0%)

1 of 2 new or added lines in 2 files covered. (50.0%)

7019 of 7283 relevant lines covered (96.38%)

3.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.71
/tests/optional.py
1
"""
2
Allows configuring optional test markers in config, see pyproject.toml.
3

4
Run optional tests with `pytest --run-optional=...`.
5

6
Mark tests to run only if an optional test ISN'T selected by prepending the mark with
7
"no_".
8

9
You can specify a "no_" prefix straight in config, in which case you can mark tests
10
to run when this tests ISN'T selected by omitting the "no_" prefix.
11

12
Specifying the name of the default behavior in `--run-optional=` is harmless.
13

14
Adapted from https://pypi.org/project/pytest-optional-tests/, (c) 2019 Reece Hart
15
"""
16

17
import itertools
4✔
18
import logging
4✔
19
import re
4✔
20
from functools import lru_cache
4✔
21
from typing import TYPE_CHECKING, FrozenSet, List, Set
4✔
22

23
import pytest
4✔
24

25
try:
4✔
26
    from pytest import StashKey
4✔
27
except ImportError:
×
28
    # pytest < 7
29
    #
30
    # "isort: skip" is needed or it moves the "type: ignore" to the following line
31
    # because of the line length, and then mypy complains.
32
    # Of course, adding the "isort: skip" means that
33
    # flake8-bugbear then also complains about the line length,
34
    # so we *also* need a "noqa" comment for good measure :)
NEW
35
    from _pytest.store import (  # type: ignore[import-not-found, no-redef]  # isort: skip  # noqa: B950
×
36
        StoreKey as StashKey,
37
    )
38

39
log = logging.getLogger(__name__)
4✔
40

41

42
if TYPE_CHECKING:
4!
43
    from _pytest.config import Config
×
44
    from _pytest.config.argparsing import Parser
×
45
    from _pytest.mark.structures import MarkDecorator
×
46
    from _pytest.nodes import Node
×
47

48

49
ALL_POSSIBLE_OPTIONAL_MARKERS = StashKey[FrozenSet[str]]()
4✔
50
ENABLED_OPTIONAL_MARKERS = StashKey[FrozenSet[str]]()
4✔
51

52

53
def pytest_addoption(parser: "Parser") -> None:
4✔
54
    group = parser.getgroup("collect")
4✔
55
    group.addoption(
4✔
56
        "--run-optional",
57
        action="append",
58
        dest="run_optional",
59
        default=None,
60
        help="Optional test markers to run; comma-separated",
61
    )
62
    parser.addini("optional-tests", "List of optional tests markers", "linelist")
4✔
63

64

65
def pytest_configure(config: "Config") -> None:
4✔
66
    """Optional tests are markers.
67

68
    Use the syntax in https://docs.pytest.org/en/stable/mark.html#registering-marks.
69
    """
70
    ot_ini = config.inicfg.get("optional-tests") or []
4✔
71
    ot_markers = set()
4✔
72
    ot_run: Set[str] = set()
4✔
73
    if isinstance(ot_ini, str):
4!
74
        ot_ini = ot_ini.strip().split("\n")
×
75
    marker_re = re.compile(r"^\s*(?P<no>no_)?(?P<marker>\w+)(:\s*(?P<description>.*))?")
4✔
76
    for ot in ot_ini:
4✔
77
        m = marker_re.match(ot)
4✔
78
        if not m:
4!
79
            raise ValueError(f"{ot!r} doesn't match pytest marker syntax")
×
80

81
        marker = (m.group("no") or "") + m.group("marker")
4✔
82
        description = m.group("description")
4✔
83
        config.addinivalue_line("markers", f"{marker}: {description}")
4✔
84
        config.addinivalue_line(
4✔
85
            "markers", f"{no(marker)}: run when `{marker}` not passed"
86
        )
87
        ot_markers.add(marker)
4✔
88

89
    # collect requested optional tests
90
    passed_args = config.getoption("run_optional")
4✔
91
    if passed_args:
4!
92
        ot_run.update(itertools.chain.from_iterable(a.split(",") for a in passed_args))
4✔
93
    ot_run |= {no(excluded) for excluded in ot_markers - ot_run}
4✔
94
    ot_markers |= {no(m) for m in ot_markers}
4✔
95

96
    log.info("optional tests to run:", ot_run)
4✔
97
    unknown_tests = ot_run - ot_markers
4✔
98
    if unknown_tests:
4!
99
        raise ValueError(f"Unknown optional tests wanted: {unknown_tests!r}")
×
100

101
    store = config._store
4✔
102
    store[ALL_POSSIBLE_OPTIONAL_MARKERS] = frozenset(ot_markers)
4✔
103
    store[ENABLED_OPTIONAL_MARKERS] = frozenset(ot_run)
4✔
104

105

106
def pytest_collection_modifyitems(config: "Config", items: "List[Node]") -> None:
4✔
107
    store = config._store
4✔
108
    all_possible_optional_markers = store[ALL_POSSIBLE_OPTIONAL_MARKERS]
4✔
109
    enabled_optional_markers = store[ENABLED_OPTIONAL_MARKERS]
4✔
110

111
    for item in items:
4✔
112
        all_markers_on_test = {m.name for m in item.iter_markers()}
4✔
113
        optional_markers_on_test = all_markers_on_test & all_possible_optional_markers
4✔
114
        if not optional_markers_on_test or (
4✔
115
            optional_markers_on_test & enabled_optional_markers
116
        ):
117
            continue
4✔
118
        log.info("skipping non-requested optional", item)
4✔
119
        item.add_marker(skip_mark(frozenset(optional_markers_on_test)))
4✔
120

121

122
@lru_cache()
4✔
123
def skip_mark(tests: FrozenSet[str]) -> "MarkDecorator":
4✔
124
    names = ", ".join(sorted(tests))
4✔
125
    return pytest.mark.skip(reason=f"Marked with disabled optional tests ({names})")
4✔
126

127

128
@lru_cache()
4✔
129
def no(name: str) -> str:
4✔
130
    if name.startswith("no_"):
4!
131
        return name[len("no_") :]
4✔
132
    return "no_" + name
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc