Coverage for /usr/local/lib/python3.7/site-packages/_pytest/nodes.py : 38%

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
1import os
2import warnings
3from functools import lru_cache
4from typing import Any
5from typing import Dict
6from typing import List
7from typing import Optional
8from typing import Set
9from typing import Tuple
10from typing import Union
12import py
14import _pytest._code
15from _pytest._code.code import ExceptionChainRepr
16from _pytest._code.code import ExceptionInfo
17from _pytest._code.code import ReprExceptionInfo
18from _pytest.compat import cached_property
19from _pytest.compat import getfslineno
20from _pytest.compat import TYPE_CHECKING
21from _pytest.config import Config
22from _pytest.fixtures import FixtureDef
23from _pytest.fixtures import FixtureLookupError
24from _pytest.fixtures import FixtureLookupErrorRepr
25from _pytest.mark.structures import Mark
26from _pytest.mark.structures import MarkDecorator
27from _pytest.mark.structures import NodeKeywords
28from _pytest.outcomes import Failed
30if TYPE_CHECKING:
31 # Imported here due to circular import.
32 from _pytest.main import Session # noqa: F401
34SEP = "/"
36tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
39@lru_cache(maxsize=None)
40def _splitnode(nodeid):
41 """Split a nodeid into constituent 'parts'.
43 Node IDs are strings, and can be things like:
44 ''
45 'testing/code'
46 'testing/code/test_excinfo.py'
47 'testing/code/test_excinfo.py::TestFormattedExcinfo'
49 Return values are lists e.g.
50 []
51 ['testing', 'code']
52 ['testing', 'code', 'test_excinfo.py']
53 ['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo']
54 """
55 if nodeid == "":
56 # If there is no root node at all, return an empty list so the caller's logic can remain sane
57 return ()
58 parts = nodeid.split(SEP)
59 # Replace single last element 'test_foo.py::Bar' with multiple elements 'test_foo.py', 'Bar'
60 parts[-1:] = parts[-1].split("::")
61 # Convert parts into a tuple to avoid possible errors with caching of a mutable type
62 return tuple(parts)
65def ischildnode(baseid, nodeid):
66 """Return True if the nodeid is a child node of the baseid.
68 E.g. 'foo/bar::Baz' is a child of 'foo', 'foo/bar' and 'foo/bar::Baz', but not of 'foo/blorp'
69 """
70 base_parts = _splitnode(baseid)
71 node_parts = _splitnode(nodeid)
72 if len(node_parts) < len(base_parts):
73 return False
74 return node_parts[: len(base_parts)] == base_parts
77class Node:
78 """ base class for Collector and Item the test collection tree.
79 Collector subclasses have children, Items are terminal nodes."""
81 def __init__(
82 self,
83 name: str,
84 parent: Optional["Node"] = None,
85 config: Optional[Config] = None,
86 session: Optional["Session"] = None,
87 fspath: Optional[py.path.local] = None,
88 nodeid: Optional[str] = None,
89 ) -> None:
90 #: a unique name within the scope of the parent node
91 self.name = name
93 #: the parent collector node.
94 self.parent = parent
96 #: the pytest config object
97 if config:
98 self.config = config
99 else:
100 if not parent:
101 raise TypeError("config or parent must be provided")
102 self.config = parent.config
104 #: the session this node is part of
105 if session:
106 self.session = session
107 else:
108 if not parent:
109 raise TypeError("session or parent must be provided")
110 self.session = parent.session
112 #: filesystem path where this node was collected from (can be None)
113 self.fspath = fspath or getattr(parent, "fspath", None)
115 #: keywords/markers collected from all scopes
116 self.keywords = NodeKeywords(self)
118 #: the marker objects belonging to this node
119 self.own_markers = [] # type: List[Mark]
121 #: allow adding of extra keywords to use for matching
122 self.extra_keyword_matches = set() # type: Set[str]
124 # used for storing artificial fixturedefs for direct parametrization
125 self._name2pseudofixturedef = {} # type: Dict[str, FixtureDef]
127 if nodeid is not None:
128 assert "::()" not in nodeid
129 self._nodeid = nodeid
130 else:
131 if not self.parent:
132 raise TypeError("nodeid or parent must be provided")
133 self._nodeid = self.parent.nodeid
134 if self.name != "()":
135 self._nodeid += "::" + self.name
137 @property
138 def ihook(self):
139 """ fspath sensitive hook proxy used to call pytest hooks"""
140 return self.session.gethookproxy(self.fspath)
142 def __repr__(self):
143 return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None))
145 def warn(self, warning):
146 """Issue a warning for this item.
148 Warnings will be displayed after the test session, unless explicitly suppressed
150 :param Warning warning: the warning instance to issue. Must be a subclass of PytestWarning.
152 :raise ValueError: if ``warning`` instance is not a subclass of PytestWarning.
154 Example usage:
156 .. code-block:: python
158 node.warn(PytestWarning("some message"))
160 """
161 from _pytest.warning_types import PytestWarning
163 if not isinstance(warning, PytestWarning):
164 raise ValueError(
165 "warning must be an instance of PytestWarning or subclass, got {!r}".format(
166 warning
167 )
168 )
169 path, lineno = get_fslocation_from_item(self)
170 warnings.warn_explicit(
171 warning,
172 category=None,
173 filename=str(path),
174 lineno=lineno + 1 if lineno is not None else None,
175 )
177 # methods for ordering nodes
178 @property
179 def nodeid(self):
180 """ a ::-separated string denoting its collection tree address. """
181 return self._nodeid
183 def __hash__(self):
184 return hash(self.nodeid)
186 def setup(self):
187 pass
189 def teardown(self):
190 pass
192 def listchain(self):
193 """ return list of all parent collectors up to self,
194 starting from root of collection tree. """
195 chain = []
196 item = self # type: Optional[Node]
197 while item is not None:
198 chain.append(item)
199 item = item.parent
200 chain.reverse()
201 return chain
203 def add_marker(
204 self, marker: Union[str, MarkDecorator], append: bool = True
205 ) -> None:
206 """dynamically add a marker object to the node.
208 :type marker: ``str`` or ``pytest.mark.*`` object
209 :param marker:
210 ``append=True`` whether to append the marker,
211 if ``False`` insert at position ``0``.
212 """
213 from _pytest.mark import MARK_GEN
215 if isinstance(marker, MarkDecorator):
216 marker_ = marker
217 elif isinstance(marker, str):
218 marker_ = getattr(MARK_GEN, marker)
219 else:
220 raise ValueError("is not a string or pytest.mark.* Marker")
221 self.keywords[marker_.name] = marker
222 if append:
223 self.own_markers.append(marker_.mark)
224 else:
225 self.own_markers.insert(0, marker_.mark)
227 def iter_markers(self, name=None):
228 """
229 :param name: if given, filter the results by the name attribute
231 iterate over all markers of the node
232 """
233 return (x[1] for x in self.iter_markers_with_node(name=name))
235 def iter_markers_with_node(self, name=None):
236 """
237 :param name: if given, filter the results by the name attribute
239 iterate over all markers of the node
240 returns sequence of tuples (node, mark)
241 """
242 for node in reversed(self.listchain()):
243 for mark in node.own_markers:
244 if name is None or getattr(mark, "name", None) == name:
245 yield node, mark
247 def get_closest_marker(self, name, default=None):
248 """return the first marker matching the name, from closest (for example function) to farther level (for example
249 module level).
251 :param default: fallback return value of no marker was found
252 :param name: name to filter by
253 """
254 return next(self.iter_markers(name=name), default)
256 def listextrakeywords(self):
257 """ Return a set of all extra keywords in self and any parents."""
258 extra_keywords = set() # type: Set[str]
259 for item in self.listchain():
260 extra_keywords.update(item.extra_keyword_matches)
261 return extra_keywords
263 def listnames(self):
264 return [x.name for x in self.listchain()]
266 def addfinalizer(self, fin):
267 """ register a function to be called when this node is finalized.
269 This method can only be called when this node is active
270 in a setup chain, for example during self.setup().
271 """
272 self.session._setupstate.addfinalizer(fin, self)
274 def getparent(self, cls):
275 """ get the next parent node (including ourself)
276 which is an instance of the given class"""
277 current = self # type: Optional[Node]
278 while current and not isinstance(current, cls):
279 current = current.parent
280 return current
282 def _prunetraceback(self, excinfo):
283 pass
285 def _repr_failure_py(
286 self, excinfo: ExceptionInfo[Union[Failed, FixtureLookupError]], style=None
287 ) -> Union[str, ReprExceptionInfo, ExceptionChainRepr, FixtureLookupErrorRepr]:
288 if isinstance(excinfo.value, Failed):
289 if not excinfo.value.pytrace:
290 return str(excinfo.value)
291 if isinstance(excinfo.value, FixtureLookupError):
292 return excinfo.value.formatrepr()
293 if self.config.getoption("fulltrace", False):
294 style = "long"
295 else:
296 tb = _pytest._code.Traceback([excinfo.traceback[-1]])
297 self._prunetraceback(excinfo)
298 if len(excinfo.traceback) == 0:
299 excinfo.traceback = tb
300 if style == "auto":
301 style = "long"
302 # XXX should excinfo.getrepr record all data and toterminal() process it?
303 if style is None:
304 if self.config.getoption("tbstyle", "auto") == "short":
305 style = "short"
306 else:
307 style = "long"
309 if self.config.getoption("verbose", 0) > 1:
310 truncate_locals = False
311 else:
312 truncate_locals = True
314 try:
315 os.getcwd()
316 abspath = False
317 except OSError:
318 abspath = True
320 return excinfo.getrepr(
321 funcargs=True,
322 abspath=abspath,
323 showlocals=self.config.getoption("showlocals", False),
324 style=style,
325 tbfilter=False, # pruned already, or in --fulltrace mode.
326 truncate_locals=truncate_locals,
327 )
329 def repr_failure(
330 self, excinfo, style=None
331 ) -> Union[str, ReprExceptionInfo, ExceptionChainRepr, FixtureLookupErrorRepr]:
332 return self._repr_failure_py(excinfo, style)
335def get_fslocation_from_item(item):
336 """Tries to extract the actual location from an item, depending on available attributes:
338 * "fslocation": a pair (path, lineno)
339 * "obj": a Python object that the item wraps.
340 * "fspath": just a path
342 :rtype: a tuple of (str|LocalPath, int) with filename and line number.
343 """
344 result = getattr(item, "location", None)
345 if result is not None:
346 return result[:2]
347 obj = getattr(item, "obj", None)
348 if obj is not None:
349 return getfslineno(obj)
350 return getattr(item, "fspath", "unknown location"), -1
353class Collector(Node):
354 """ Collector instances create children through collect()
355 and thus iteratively build a tree.
356 """
358 class CollectError(Exception):
359 """ an error during collection, contains a custom message. """
361 def collect(self):
362 """ returns a list of children (items and collectors)
363 for this collection node.
364 """
365 raise NotImplementedError("abstract")
367 def repr_failure(self, excinfo):
368 """ represent a collection failure. """
369 if excinfo.errisinstance(self.CollectError):
370 exc = excinfo.value
371 return str(exc.args[0])
373 # Respect explicit tbstyle option, but default to "short"
374 # (None._repr_failure_py defaults to "long" without "fulltrace" option).
375 tbstyle = self.config.getoption("tbstyle", "auto")
376 if tbstyle == "auto":
377 tbstyle = "short"
379 return self._repr_failure_py(excinfo, style=tbstyle)
381 def _prunetraceback(self, excinfo):
382 if hasattr(self, "fspath"):
383 traceback = excinfo.traceback
384 ntraceback = traceback.cut(path=self.fspath)
385 if ntraceback == traceback:
386 ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
387 excinfo.traceback = ntraceback.filter()
390def _check_initialpaths_for_relpath(session, fspath):
391 for initial_path in session._initialpaths:
392 if fspath.common(initial_path) == initial_path:
393 return fspath.relto(initial_path)
396class FSCollector(Collector):
397 def __init__(
398 self, fspath: py.path.local, parent=None, config=None, session=None, nodeid=None
399 ) -> None:
400 name = fspath.basename
401 if parent is not None:
402 rel = fspath.relto(parent.fspath)
403 if rel:
404 name = rel
405 name = name.replace(os.sep, SEP)
406 self.fspath = fspath
408 session = session or parent.session
410 if nodeid is None:
411 nodeid = self.fspath.relto(session.config.rootdir)
413 if not nodeid:
414 nodeid = _check_initialpaths_for_relpath(session, fspath)
415 if nodeid and os.sep != SEP:
416 nodeid = nodeid.replace(os.sep, SEP)
418 super().__init__(name, parent, config, session, nodeid=nodeid, fspath=fspath)
421class File(FSCollector):
422 """ base class for collecting tests from a file. """
425class Item(Node):
426 """ a basic test invocation item. Note that for a single function
427 there might be multiple test invocation items.
428 """
430 nextitem = None
432 def __init__(self, name, parent=None, config=None, session=None, nodeid=None):
433 super().__init__(name, parent, config, session, nodeid=nodeid)
434 self._report_sections = [] # type: List[Tuple[str, str, str]]
436 #: user properties is a list of tuples (name, value) that holds user
437 #: defined properties for this test.
438 self.user_properties = [] # type: List[Tuple[str, Any]]
440 def add_report_section(self, when: str, key: str, content: str) -> None:
441 """
442 Adds a new report section, similar to what's done internally to add stdout and
443 stderr captured output::
445 item.add_report_section("call", "stdout", "report section contents")
447 :param str when:
448 One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.
449 :param str key:
450 Name of the section, can be customized at will. Pytest uses ``"stdout"`` and
451 ``"stderr"`` internally.
453 :param str content:
454 The full contents as a string.
455 """
456 if content:
457 self._report_sections.append((when, key, content))
459 def reportinfo(self) -> Tuple[Union[py.path.local, str], Optional[int], str]:
460 return self.fspath, None, ""
462 @cached_property
463 def location(self) -> Tuple[str, Optional[int], str]:
464 location = self.reportinfo()
465 fspath = self.session._node_location_to_relpath(location[0])
466 assert type(location[2]) is str
467 return (fspath, location[1], location[2])