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

psf / black / 19463612781

18 Nov 2025 10:56AM UTC coverage: 95.889% (-0.03%) from 95.915%
19463612781

Pull #4749

github

web-flow
Merge ecd954bbc into 45b408797
Pull Request #4749: Upgrade mypy

5130 of 5402 branches covered (94.96%)

7861 of 8198 relevant lines covered (95.89%)

4.79 hits per line

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

87.67
/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
5✔
18
import logging
5✔
19
import re
5✔
20
from functools import lru_cache
5✔
21
from typing import TYPE_CHECKING, Any
5✔
22

23
import pytest
5✔
24
from pytest import StashKey
5✔
25

26
log = logging.getLogger(__name__)
5✔
27

28

29
if TYPE_CHECKING:
5!
30
    from _pytest.config import Config
×
31
    from _pytest.config.argparsing import Parser
×
32
    from _pytest.mark.structures import MarkDecorator
×
33
    from _pytest.nodes import Node
×
34

35

36
ALL_POSSIBLE_OPTIONAL_MARKERS = StashKey[frozenset[str]]()
5✔
37
ENABLED_OPTIONAL_MARKERS = StashKey[frozenset[str]]()
5✔
38

39

40
def pytest_addoption(parser: "Parser") -> None:
5✔
41
    group = parser.getgroup("collect")
5✔
42
    group.addoption(
5✔
43
        "--run-optional",
44
        action="append",
45
        dest="run_optional",
46
        default=None,
47
        help="Optional test markers to run; comma-separated",
48
    )
49
    parser.addini("optional-tests", "List of optional tests markers", "linelist")
5✔
50

51

52
def pytest_configure(config: "Config") -> None:
5✔
53
    """Optional tests are markers.
54

55
    Use the syntax in https://docs.pytest.org/en/stable/mark.html#registering-marks.
56
    """
57
    # Extract the configured optional-tests from pytest's ini config in a
58
    # version-agnostic way. Depending on pytest version, the value can be a
59
    # string, a list of strings, or a ConfigValue wrapper (with a `.value` attr).
60
    raw_ot_ini: Any = config.inicfg.get("optional-tests")
5✔
61
    ot_ini_lines: list[str] = []
5✔
62
    if raw_ot_ini:
5!
63
        value = getattr(raw_ot_ini, "value", raw_ot_ini)
5✔
64
        if isinstance(value, str):
5!
65
            ot_ini_lines = value.strip().split("\n")
×
66
        elif isinstance(value, list):
5!
67
            # Best-effort coercion to strings; pytest inis are textual.
68
            ot_ini_lines = [str(v) for v in value]
5✔
69
        else:
70
            # Fallback: ignore unexpected shapes (non-iterable, etc.).
71
            ot_ini_lines = []
×
72

73
    ot_markers: set[str] = set()
5✔
74
    ot_run: set[str] = set()
5✔
75
    marker_re = re.compile(r"^\s*(?P<no>no_)?(?P<marker>\w+)(:\s*(?P<description>.*))?")
5✔
76
    # Iterate over configured markers discovered above.
77
    for ot in ot_ini_lines:
5✔
78
        m = marker_re.match(ot)
5✔
79
        if not m:
5!
80
            raise ValueError(f"{ot!r} doesn't match pytest marker syntax")
×
81

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

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

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

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

106

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

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

122

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

128

129
@lru_cache
5✔
130
def no(name: str) -> str:
5✔
131
    if name.startswith("no_"):
5!
132
        return name[len("no_") :]
5✔
133
    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

© 2026 Coveralls, Inc