Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/_pytest/mark/__init__.py : 34%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1""" generic mechanism for marking and selecting python functions. """
2import typing
3from typing import AbstractSet
4from typing import List
5from typing import Optional
6from typing import Union
8import attr
10from .expression import Expression
11from .expression import ParseError
12from .structures import EMPTY_PARAMETERSET_OPTION
13from .structures import get_empty_parameterset_mark
14from .structures import Mark
15from .structures import MARK_GEN
16from .structures import MarkDecorator
17from .structures import MarkGenerator
18from .structures import ParameterSet
19from _pytest.compat import TYPE_CHECKING
20from _pytest.config import Config
21from _pytest.config import ExitCode
22from _pytest.config import hookimpl
23from _pytest.config import UsageError
24from _pytest.config.argparsing import Parser
25from _pytest.store import StoreKey
27if TYPE_CHECKING:
28 from _pytest.nodes import Item
31__all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"]
34old_mark_config_key = StoreKey[Optional[Config]]()
37def param(
38 *values: object,
39 marks: "Union[MarkDecorator, typing.Collection[Union[MarkDecorator, Mark]]]" = (),
40 id: Optional[str] = None
41) -> ParameterSet:
42 """Specify a parameter in `pytest.mark.parametrize`_ calls or
43 :ref:`parametrized fixtures <fixture-parametrize-marks>`.
45 .. code-block:: python
47 @pytest.mark.parametrize(
48 "test_input,expected",
49 [("3+5", 8), pytest.param("6*9", 42, marks=pytest.mark.xfail),],
50 )
51 def test_eval(test_input, expected):
52 assert eval(test_input) == expected
54 :param values: variable args of the values of the parameter set, in order.
55 :keyword marks: a single mark or a list of marks to be applied to this parameter set.
56 :keyword str id: the id to attribute to this parameter set.
57 """
58 return ParameterSet.param(*values, marks=marks, id=id)
61def pytest_addoption(parser: Parser) -> None:
62 group = parser.getgroup("general")
63 group._addoption(
64 "-k",
65 action="store",
66 dest="keyword",
67 default="",
68 metavar="EXPRESSION",
69 help="only run tests which match the given substring expression. "
70 "An expression is a python evaluatable expression "
71 "where all names are substring-matched against test names "
72 "and their parent classes. Example: -k 'test_method or test_"
73 "other' matches all test functions and classes whose name "
74 "contains 'test_method' or 'test_other', while -k 'not test_method' "
75 "matches those that don't contain 'test_method' in their names. "
76 "-k 'not test_method and not test_other' will eliminate the matches. "
77 "Additionally keywords are matched to classes and functions "
78 "containing extra names in their 'extra_keyword_matches' set, "
79 "as well as functions which have names assigned directly to them. "
80 "The matching is case-insensitive.",
81 )
83 group._addoption(
84 "-m",
85 action="store",
86 dest="markexpr",
87 default="",
88 metavar="MARKEXPR",
89 help="only run tests matching given mark expression.\n"
90 "For example: -m 'mark1 and not mark2'.",
91 )
93 group.addoption(
94 "--markers",
95 action="store_true",
96 help="show markers (builtin, plugin and per-project ones).",
97 )
99 parser.addini("markers", "markers for test functions", "linelist")
100 parser.addini(EMPTY_PARAMETERSET_OPTION, "default marker for empty parametersets")
103@hookimpl(tryfirst=True)
104def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
105 import _pytest.config
107 if config.option.markers:
108 config._do_configure()
109 tw = _pytest.config.create_terminal_writer(config)
110 for line in config.getini("markers"):
111 parts = line.split(":", 1)
112 name = parts[0]
113 rest = parts[1] if len(parts) == 2 else ""
114 tw.write("@pytest.mark.%s:" % name, bold=True)
115 tw.line(rest)
116 tw.line()
117 config._ensure_unconfigure()
118 return 0
120 return None
123@attr.s(slots=True)
124class KeywordMatcher:
125 """A matcher for keywords.
127 Given a list of names, matches any substring of one of these names. The
128 string inclusion check is case-insensitive.
130 Will match on the name of colitem, including the names of its parents.
131 Only matches names of items which are either a :class:`Class` or a
132 :class:`Function`.
134 Additionally, matches on names in the 'extra_keyword_matches' set of
135 any item, as well as names directly assigned to test functions.
136 """
138 _names = attr.ib(type=AbstractSet[str])
140 @classmethod
141 def from_item(cls, item: "Item") -> "KeywordMatcher":
142 mapped_names = set()
144 # Add the names of the current item and any parent items
145 import pytest
147 for node in item.listchain():
148 if not isinstance(node, (pytest.Instance, pytest.Session)):
149 mapped_names.add(node.name)
151 # Add the names added as extra keywords to current or parent items
152 mapped_names.update(item.listextrakeywords())
154 # Add the names attached to the current function through direct assignment
155 function_obj = getattr(item, "function", None)
156 if function_obj:
157 mapped_names.update(function_obj.__dict__)
159 # add the markers to the keywords as we no longer handle them correctly
160 mapped_names.update(mark.name for mark in item.iter_markers())
162 return cls(mapped_names)
164 def __call__(self, subname: str) -> bool:
165 subname = subname.lower()
166 names = (name.lower() for name in self._names)
168 for name in names:
169 if subname in name:
170 return True
171 return False
174def deselect_by_keyword(items: "List[Item]", config: Config) -> None:
175 keywordexpr = config.option.keyword.lstrip()
176 if not keywordexpr:
177 return
179 if keywordexpr.startswith("-"):
180 # To be removed in pytest 7.0.0.
181 # Uncomment this after 6.0 release (#7361)
182 # warnings.warn(MINUS_K_DASH, stacklevel=2)
183 keywordexpr = "not " + keywordexpr[1:]
184 selectuntil = False
185 if keywordexpr[-1:] == ":":
186 # To be removed in pytest 7.0.0.
187 # Uncomment this after 6.0 release (#7361)
188 # warnings.warn(MINUS_K_COLON, stacklevel=2)
189 selectuntil = True
190 keywordexpr = keywordexpr[:-1]
192 try:
193 expression = Expression.compile(keywordexpr)
194 except ParseError as e:
195 raise UsageError(
196 "Wrong expression passed to '-k': {}: {}".format(keywordexpr, e)
197 ) from None
199 remaining = []
200 deselected = []
201 for colitem in items:
202 if keywordexpr and not expression.evaluate(KeywordMatcher.from_item(colitem)):
203 deselected.append(colitem)
204 else:
205 if selectuntil:
206 keywordexpr = None
207 remaining.append(colitem)
209 if deselected:
210 config.hook.pytest_deselected(items=deselected)
211 items[:] = remaining
214@attr.s(slots=True)
215class MarkMatcher:
216 """A matcher for markers which are present.
218 Tries to match on any marker names, attached to the given colitem.
219 """
221 own_mark_names = attr.ib()
223 @classmethod
224 def from_item(cls, item) -> "MarkMatcher":
225 mark_names = {mark.name for mark in item.iter_markers()}
226 return cls(mark_names)
228 def __call__(self, name: str) -> bool:
229 return name in self.own_mark_names
232def deselect_by_mark(items: "List[Item]", config: Config) -> None:
233 matchexpr = config.option.markexpr
234 if not matchexpr:
235 return
237 try:
238 expression = Expression.compile(matchexpr)
239 except ParseError as e:
240 raise UsageError(
241 "Wrong expression passed to '-m': {}: {}".format(matchexpr, e)
242 ) from None
244 remaining = []
245 deselected = []
246 for item in items:
247 if expression.evaluate(MarkMatcher.from_item(item)):
248 remaining.append(item)
249 else:
250 deselected.append(item)
252 if deselected:
253 config.hook.pytest_deselected(items=deselected)
254 items[:] = remaining
257def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None:
258 deselect_by_keyword(items, config)
259 deselect_by_mark(items, config)
262def pytest_configure(config: Config) -> None:
263 config._store[old_mark_config_key] = MARK_GEN._config
264 MARK_GEN._config = config
266 empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION)
268 if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""):
269 raise UsageError(
270 "{!s} must be one of skip, xfail or fail_at_collect"
271 " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)
272 )
275def pytest_unconfigure(config: Config) -> None:
276 MARK_GEN._config = config._store.get(old_mark_config_key, None)