diff --git a/_pytest/mark/structures.py b/_pytest/mark/structures.py index c56972980..fe0e1983d 100644 --- a/_pytest/mark/structures.py +++ b/_pytest/mark/structures.py @@ -365,3 +365,28 @@ class NodeKeywords(MappingMixin): def __repr__(self): return "" % (self.node, ) + + +@attr.s(cmp=False, hash=False) +class NodeMarkers(object): + node = attr.ib(repr=False) + own_markers = attr.ib(default=attr.Factory(list)) + + @classmethod + def from_node(cls, node): + return cls(node=node) + + def update(self, add_markers): + """update the own markers + """ + self.own_markers.extend(add_markers) + + def find(self, name): + """ + find markers in own nodes or parent nodes + needs a better place + """ + for node in reversed(self.node.listchain()): + for mark in node._markers.own_markers: + if mark.name == name: + yield mark diff --git a/_pytest/nodes.py b/_pytest/nodes.py index 97f4da602..e7686b39f 100644 --- a/_pytest/nodes.py +++ b/_pytest/nodes.py @@ -4,11 +4,12 @@ import os import six import py import attr +from more_itertools import first import _pytest import _pytest._code -from _pytest.mark.structures import NodeKeywords +from _pytest.mark.structures import NodeKeywords, NodeMarkers SEP = "/" @@ -89,6 +90,7 @@ class Node(object): #: keywords/markers collected from all scopes self.keywords = NodeKeywords(self) + self._markers = NodeMarkers.from_node(self) #: allow adding of extra keywords to use for matching self.extra_keyword_matches = set() @@ -178,15 +180,16 @@ class Node(object): elif not isinstance(marker, MarkDecorator): raise ValueError("is not a string or pytest.mark.* Marker") self.keywords[marker.name] = marker + self._markers.update([marker]) + + def find_markers(self, name): + """find all marks with the given name on the node and its parents""" + return self._markers.find(name) def get_marker(self, name): """ get a marker object from this node or None if the node doesn't have a marker with that name. """ - val = self.keywords.get(name, None) - if val is not None: - from _pytest.mark import MarkInfo, MarkDecorator - if isinstance(val, (MarkDecorator, MarkInfo)): - return val + return first(self.find_markers(name), None) def listextrakeywords(self): """ Return a set of all extra keywords in self and any parents.""" diff --git a/_pytest/python.py b/_pytest/python.py index f9f17afd7..f7d4c601c 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -28,7 +28,7 @@ from _pytest.compat import ( safe_str, getlocation, enum, ) from _pytest.outcomes import fail -from _pytest.mark.structures import transfer_markers +from _pytest.mark.structures import transfer_markers, get_unpacked_marks # relative paths that we use to filter traceback entries from appearing to the user; @@ -212,11 +212,17 @@ class PyobjContext(object): class PyobjMixin(PyobjContext): + + def __init__(self, *k, **kw): + super(PyobjMixin, self).__init__(*k, **kw) + def obj(): def fget(self): obj = getattr(self, '_obj', None) if obj is None: self._obj = obj = self._getobj() + # XXX evil hacn + self._markers.update(get_unpacked_marks(self.obj)) return obj def fset(self, value): @@ -1114,6 +1120,7 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr): self.obj = callobj self.keywords.update(self.obj.__dict__) + self._markers.update(get_unpacked_marks(self.obj)) if callspec: self.callspec = callspec # this is total hostile and a mess @@ -1123,6 +1130,7 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr): # feel free to cry, this was broken for years before # and keywords cant fix it per design self.keywords[mark.name] = mark + self._markers.update(callspec.marks) if keywords: self.keywords.update(keywords)