Skip to content

Commit

Permalink
introduce name filtering for marker iteration again
Browse files Browse the repository at this point in the history
  • Loading branch information
RonnyPfannschmidt committed May 11, 2018
1 parent 35f53a7 commit 4914135
Show file tree
Hide file tree
Showing 11 changed files with 67 additions and 33 deletions.
2 changes: 1 addition & 1 deletion _pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,7 @@ def getfixtureinfo(self, node, func, cls, funcargs=True):
argnames = getfuncargnames(func, cls=cls)
else:
argnames = ()
usefixtures = flatten(mark.args for mark in node.iter_markers() if mark.name == "usefixtures")
usefixtures = flatten(mark.args for mark in node.iter_markers(name="usefixtures"))
initialnames = argnames
initialnames = tuple(usefixtures) + initialnames
fm = node.session._fixturemanager
Expand Down
2 changes: 1 addition & 1 deletion _pytest/mark/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def wasvalid(self):
return not hasattr(self, 'exc')

def _get_marks(self):
return [x for x in self.item.iter_markers() if x.name == self._mark_name]
return list(self.item.iter_markers(name=self._mark_name))

def invalidraise(self, exc):
raises = self.get('raises')
Expand Down
22 changes: 17 additions & 5 deletions _pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,20 +183,32 @@ def add_marker(self, marker):
self.keywords[marker.name] = marker
self.own_markers.append(marker)

def iter_markers(self):
def iter_markers(self, name=None):
"""
:param name: if given, filter the results by the name attribute
iterate over all markers of the node
"""
return (x[1] for x in self.iter_markers_with_node())
return (x[1] for x in self.iter_markers_with_node(name=name))

def iter_markers_with_node(self):
def iter_markers_with_node(self, name=None):
"""
:param name: if given, filter the results by the name attribute
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
if name is None or getattr(mark, 'name', None) == name:
yield node, mark

def get_closest_marker(self, name, default=None):
"""return the first marker matching the name
:param default: fallback return value of no marker was found
:param name: name to filter by
"""
return next(self.iter_markers(name=name), default)

def get_marker(self, name):
""" get a marker object from this node or None if
Expand All @@ -206,7 +218,7 @@ def get_marker(self, name):
deprecated
"""
markers = [x for x in self.iter_markers() if x.name == name]
markers = list(self.iter_markers(name=name))
if markers:
return MarkInfo(markers)

Expand Down
5 changes: 2 additions & 3 deletions _pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,8 @@ def pytest_generate_tests(metafunc):
if hasattr(metafunc.function, attr):
msg = "{0} has '{1}', spelling should be 'parametrize'"
raise MarkerError(msg.format(metafunc.function.__name__, attr))
for marker in metafunc.definition.iter_markers():
if marker.name == 'parametrize':
metafunc.parametrize(*marker.args, **marker.kwargs)
for marker in metafunc.definition.iter_markers(name='parametrize'):
metafunc.parametrize(*marker.args, **marker.kwargs)


def pytest_configure(config):
Expand Down
4 changes: 1 addition & 3 deletions _pytest/skipping.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ def pytest_runtest_setup(item):
item._skipped_by_mark = True
skip(eval_skipif.getexplanation())

for skip_info in item.iter_markers():
if skip_info.name != 'skip':
continue
for skip_info in item.iter_markers(name='skip'):
item._skipped_by_mark = True
if 'reason' in skip_info.kwargs:
skip(skip_info.kwargs['reason'])
Expand Down
7 changes: 3 additions & 4 deletions _pytest/warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ def catch_warnings_for_item(item):
for arg in inifilters:
_setoption(warnings, arg)

for mark in item.iter_markers():
if mark.name == 'filterwarnings':
for arg in mark.args:
warnings._setoption(arg)
for mark in item.iter_markers(name='filterwarnings'):
for arg in mark.args:
warnings._setoption(arg)

yield

Expand Down
1 change: 1 addition & 0 deletions changelog/3446.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
introduce node.get_closest_marker(name, default=None) to support simple marker usage setups.
1 change: 1 addition & 0 deletions changelog/3459.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Introduce optional name based filtering for iter_markers
16 changes: 7 additions & 9 deletions doc/en/example/markers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ specifies via named environments::
"env(name): mark test to run only on named environment")

def pytest_runtest_setup(item):
envnames = [mark.args[0] for mark in item.iter_markers() if mark.name == "env"]
envnames = [mark.args[0] for mark in item.iter_markers(name='env')]
if envnames:
if item.config.getoption("-E") not in envnames:
pytest.skip("test requires env in %r" % envnames)
Expand Down Expand Up @@ -402,10 +402,9 @@ Below is the config file that will be used in the next examples::
import sys

def pytest_runtest_setup(item):
for marker in item.iter_markers():
if marker.name == 'my_marker':
print(marker)
sys.stdout.flush()
for marker in item.iter_markers(name='my_marker'):
print(marker)
sys.stdout.flush()

A custom marker can have its argument set, i.e. ``args`` and ``kwargs`` properties, defined by either invoking it as a callable or using ``pytest.mark.MARKER_NAME.with_args``. These two methods achieve the same effect most of the time.

Expand Down Expand Up @@ -458,10 +457,9 @@ test function. From a conftest file we can read it like this::
import sys

def pytest_runtest_setup(item):
for mark in item.iter_markers():
if mark.name == 'glob':
print ("glob args=%s kwargs=%s" %(mark.args, mark.kwargs))
sys.stdout.flush()
for mark in item.iter_markers(name='glob'):
print ("glob args=%s kwargs=%s" %(mark.args, mark.kwargs))
sys.stdout.flush()

Let's run this without capturing output and see what we get::

Expand Down
7 changes: 3 additions & 4 deletions doc/en/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,9 @@ Alternatively, you can integrate this functionality with custom markers:
def pytest_collection_modifyitems(session, config, items):
for item in items:
for marker in item.iter_markers():
if marker.name == 'test_id':
test_id = marker.args[0]
item.user_properties.append(('test_id', test_id))
for marker in item.iter_markers(name='test_id'):
test_id = marker.args[0]
item.user_properties.append(('test_id', test_id))
And in your tests:

Expand Down
33 changes: 30 additions & 3 deletions testing/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,6 @@ def test_bar(self): pass
self.assert_markers(items, test_foo=('a', 'b'), test_bar=('a',))

@pytest.mark.issue568
@pytest.mark.xfail(reason="markers smear on methods of base classes")
def test_mark_should_not_pass_to_siebling_class(self, testdir):
p = testdir.makepyfile("""
import pytest
Expand All @@ -573,8 +572,16 @@ class TestOtherSub(TestBase):
""")
items, rec = testdir.inline_genitems(p)
base_item, sub_item, sub_item_other = items
assert not hasattr(base_item.obj, 'b')
assert not hasattr(sub_item_other.obj, 'b')
print(items, [x.nodeid for x in items])
# legacy api smears
assert hasattr(base_item.obj, 'b')
assert hasattr(sub_item_other.obj, 'b')
assert hasattr(sub_item.obj, 'b')

# new api seregates
assert not list(base_item.iter_markers(name='b'))
assert not list(sub_item_other.iter_markers(name='b'))
assert list(sub_item.iter_markers(name='b'))

def test_mark_decorator_baseclasses_merged(self, testdir):
p = testdir.makepyfile("""
Expand All @@ -598,6 +605,26 @@ def test_bar(self): pass
self.assert_markers(items, test_foo=('a', 'b', 'c'),
test_bar=('a', 'b', 'd'))

def test_mark_closest(self, testdir):
p = testdir.makepyfile("""
import pytest
@pytest.mark.c(location="class")
class Test:
@pytest.mark.c(location="function")
def test_has_own():
pass
def test_has_inherited():
pass
""")
items, rec = testdir.inline_genitems(p)
has_own, has_inherited = items
assert has_own.get_closest_marker('c').kwargs == {'location': 'function'}
assert has_inherited.get_closest_marker('c').kwargs == {'location': 'class'}
assert has_own.get_closest_marker('missing') is None

def test_mark_with_wrong_marker(self, testdir):
reprec = testdir.inline_runsource("""
import pytest
Expand Down

0 comments on commit 4914135

Please sign in to comment.