Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introduce a distinct searchable non-broken storage for markers #3317

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
360d608
introduce a own storage for markers
RonnyPfannschmidt Mar 16, 2018
2707221
port mark evaluation to the new storage and fix a bug in evaluation
RonnyPfannschmidt Mar 16, 2018
f1a1695
enable deep merging test - new structure fixed it
RonnyPfannschmidt Mar 16, 2018
e8feee0
fix up the mark evaluator validity check
RonnyPfannschmidt Mar 16, 2018
180ae09
deprecate markinfo and fix up most marker scoping access while comple…
RonnyPfannschmidt Mar 17, 2018
99015bf
fix most of metafunc tests by mocking
RonnyPfannschmidt Mar 17, 2018
2d06ae0
base metafunc fixtureinfo on the functiondefinition to caputure its m…
RonnyPfannschmidt Mar 17, 2018
5e56e9b
refactor node markers, remove need to be aware of nodes
RonnyPfannschmidt Mar 18, 2018
ced1316
add docstrings for nodemarkers
RonnyPfannschmidt Mar 19, 2018
775fb96
first changelog entry
RonnyPfannschmidt Mar 19, 2018
159ea9b
turn Markinfo into atts clsas, and return correct instances of it fro…
RonnyPfannschmidt Mar 19, 2018
a92a51b
clarify find_markers return value
RonnyPfannschmidt Mar 20, 2018
02315c0
remove unnecessary of in the code figuring the fixture names
RonnyPfannschmidt Mar 21, 2018
2cb7e72
document the hack used to avoid duplicate markers due Instance collec…
RonnyPfannschmidt Mar 21, 2018
ee51fa5
add api to iterate over all marerks of a node
RonnyPfannschmidt Mar 26, 2018
8805036
add node iteration apis
RonnyPfannschmidt Mar 26, 2018
dbb1b5a
remove NodeMarkers, turn own_markers into a list and use iter_markers…
RonnyPfannschmidt Mar 29, 2018
802da78
fix method reference to iter_markers in warning
RonnyPfannschmidt Mar 30, 2018
e4a52c1
prevent doubleing of function level marks
RonnyPfannschmidt Mar 30, 2018
7454a38
update configuration examples to new mark api
RonnyPfannschmidt Mar 30, 2018
a2974dd
fix doc building
RonnyPfannschmidt Mar 30, 2018
1fcadeb
extend marker docs with reasons on marker iteration
RonnyPfannschmidt Apr 5, 2018
48bcc34
Reword the docs on markers a bit
nicoddemus Apr 5, 2018
a8ad89c
fix documentation references
RonnyPfannschmidt Apr 5, 2018
3582e1f
include more detail on the marker api issues
RonnyPfannschmidt Apr 5, 2018
e534cc8
Fix typos in docs
nicoddemus Apr 5, 2018
4df8f2b
fix doc build, use noindex on the mark reference
RonnyPfannschmidt Apr 6, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion _pytest/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ class RemovedInPytest4Warning(DeprecationWarning):
)

MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
"MarkInfo objects are deprecated as they contain the merged marks"
"MarkInfo objects are deprecated as they contain the merged marks.\n"
"Please use node.iter_markers to iterate over markers correctly"
)

MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
Expand Down
13 changes: 6 additions & 7 deletions _pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys
import warnings
from collections import OrderedDict, deque, defaultdict
from more_itertools import flatten

import attr
import py
Expand Down Expand Up @@ -371,10 +372,7 @@ def applymarker(self, marker):
:arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
created by a call to ``pytest.mark.NAME(...)``.
"""
try:
self.node.keywords[marker.markname] = marker
except AttributeError:
raise ValueError(marker)
self.node.add_marker(marker)

def raiseerror(self, msg):
""" raise a FixtureLookupError with the given message. """
Expand Down Expand Up @@ -985,10 +983,9 @@ def getfixtureinfo(self, node, func, cls, funcargs=True):
argnames = getfuncargnames(func, cls=cls)
else:
argnames = ()
usefixtures = getattr(func, "usefixtures", None)
usefixtures = flatten(mark.args for mark in node.iter_markers() if mark.name == "usefixtures")
initialnames = argnames
if usefixtures is not None:
initialnames = usefixtures.args + initialnames
initialnames = tuple(usefixtures) + initialnames
fm = node.session._fixturemanager
names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames,
node)
Expand Down Expand Up @@ -1070,6 +1067,8 @@ def pytest_generate_tests(self, metafunc):
fixturedef = faclist[-1]
if fixturedef.params is not None:
parametrize_func = getattr(metafunc.function, 'parametrize', None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use find_markers as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes and no, im at it now and its a very brittle part of fixtures i dont fully understand,
its currently implemented using the old mechanism, and i'm afraid to touch it as its so messy

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i tracked it back to c462393

as far as i can tell it has been incorrect the whole time and i cant fix that incorrectness, but i should use find_markers

im on it now

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

about 3 different attempts to fox this with a normal loop failed, its not clear how to correctly fix at first glance, as such i will leave that code as is for now and revisit later

if parametrize_func is not None:
parametrize_func = parametrize_func.combined
func_params = getattr(parametrize_func, 'args', [[None]])
func_kwargs = getattr(parametrize_func, 'kwargs', {})
# skip directly parametrized arguments
Expand Down
14 changes: 3 additions & 11 deletions _pytest/mark/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import platform
import traceback

from . import MarkDecorator, MarkInfo
from ..outcomes import fail, TEST_OUTCOME


Expand All @@ -28,22 +27,15 @@ def __init__(self, item, name):
self._mark_name = name

def __bool__(self):
self._marks = self._get_marks()
return bool(self._marks)
# dont cache here to prevent staleness
return bool(self._get_marks())
__nonzero__ = __bool__

def wasvalid(self):
return not hasattr(self, 'exc')

def _get_marks(self):

keyword = self.item.keywords.get(self._mark_name)
if isinstance(keyword, MarkDecorator):
return [keyword.mark]
elif isinstance(keyword, MarkInfo):
return [x.combined for x in keyword]
else:
return []
return [x for x in self.item.iter_markers() if x.name == self._mark_name]

def invalidraise(self, exc):
raises = self.get('raises')
Expand Down
79 changes: 63 additions & 16 deletions _pytest/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
import inspect

import attr
from ..deprecated import MARK_PARAMETERSET_UNPACKING

from ..deprecated import MARK_PARAMETERSET_UNPACKING, MARK_INFO_ATTRIBUTE
from ..compat import NOTSET, getfslineno
from six.moves import map
from six.moves import map, reduce


EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
Expand Down Expand Up @@ -113,11 +114,21 @@ def _for_parametrize(cls, argnames, argvalues, func, config):

@attr.s(frozen=True)
class Mark(object):
name = attr.ib()
args = attr.ib()
kwargs = attr.ib()
#: name of the mark
name = attr.ib(type=str)
#: positional arguments of the mark decorator
args = attr.ib(type="List[object]")
#: keyword arguments of the mark decorator
kwargs = attr.ib(type="Dict[str, object]")

def combined_with(self, other):
"""
:param other: the mark to combine with
:type other: Mark
:rtype: Mark

combines by appending aargs and merging the mappings
"""
assert self.name == other.name
return Mark(
self.name, self.args + other.args,
Expand Down Expand Up @@ -233,7 +244,7 @@ def store_legacy_markinfo(func, mark):
raise TypeError("got {mark!r} instead of a Mark".format(mark=mark))
holder = getattr(func, mark.name, None)
if holder is None:
holder = MarkInfo(mark)
holder = MarkInfo.for_mark(mark)
setattr(func, mark.name, holder)
else:
holder.add_mark(mark)
Expand All @@ -260,23 +271,29 @@ def _marked(func, mark):
invoked more than once.
"""
try:
func_mark = getattr(func, mark.name)
func_mark = getattr(func, getattr(mark, 'combined', mark).name)
except AttributeError:
return False
return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
return any(mark == info.combined for info in func_mark)


@attr.s
class MarkInfo(object):
""" Marking object created by :class:`MarkDecorator` instances. """

def __init__(self, mark):
assert isinstance(mark, Mark), repr(mark)
self.combined = mark
self._marks = [mark]
_marks = attr.ib()
combined = attr.ib(
repr=False,
default=attr.Factory(lambda self: reduce(Mark.combined_with, self._marks),
takes_self=True))

name = alias('combined.name')
args = alias('combined.args')
kwargs = alias('combined.kwargs')
name = alias('combined.name', warning=MARK_INFO_ATTRIBUTE)
args = alias('combined.args', warning=MARK_INFO_ATTRIBUTE)
kwargs = alias('combined.kwargs', warning=MARK_INFO_ATTRIBUTE)

@classmethod
def for_mark(cls, mark):
return cls([mark])

def __repr__(self):
return "<MarkInfo {0!r}>".format(self.combined)
Expand All @@ -288,7 +305,7 @@ def add_mark(self, mark):

def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """
return map(MarkInfo, self._marks)
return map(MarkInfo.for_mark, self._marks)


class MarkGenerator(object):
Expand Down Expand Up @@ -365,3 +382,33 @@ def __len__(self):

def __repr__(self):
return "<NodeKeywords for node %s>" % (self.node, )


@attr.s(cmp=False, hash=False)
class NodeMarkers(object):
"""
internal strucutre for storing marks belongong to a node
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be added to the docs in reference.rst?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think so, it should be noted that it currently cant be accessed via public attributes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's the case, then it doesn't need adding to the docs does it?


..warning::

unstable api

"""
own_markers = attr.ib(default=attr.Factory(list))

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 mark in self.own_markers:
if mark.name == name:
yield mark

def __iter__(self):
return iter(self.own_markers)
36 changes: 29 additions & 7 deletions _pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import _pytest
import _pytest._code

from _pytest.mark.structures import NodeKeywords
from _pytest.mark.structures import NodeKeywords, MarkInfo

SEP = "/"

Expand Down Expand Up @@ -90,6 +90,9 @@ def __init__(self, name, parent=None, config=None, session=None, fspath=None, no
#: keywords/markers collected from all scopes
self.keywords = NodeKeywords(self)

#: the marker objects belonging to this node
self.own_markers = []

#: allow adding of extra keywords to use for matching
self.extra_keyword_matches = set()

Expand Down Expand Up @@ -178,15 +181,34 @@ def add_marker(self, marker):
elif not isinstance(marker, MarkDecorator):
raise ValueError("is not a string or pytest.mark.* Marker")
self.keywords[marker.name] = marker
self.own_markers.append(marker)

def iter_markers(self):
"""
iterate over all markers of the node
"""
return (x[1] for x in self.iter_markers_with_node())

def iter_markers_with_node(self):
"""
iterate over all markers of the node
returns sequence of tuples (node, mark)
"""
for node in reversed(self.listchain()):
for mark in node.own_markers:
yield node, mark

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
the node doesn't have a marker with that name.

..warning::

deprecated
"""
markers = [x for x in self.iter_markers() if x.name == name]
if markers:
return MarkInfo(markers)

def listextrakeywords(self):
""" Return a set of all extra keywords in self and any parents."""
Expand Down
Loading