introduce name filtering for marker iteration again

This commit is contained in:
Ronny Pfannschmidt 2018-05-09 15:40:52 +02:00
parent 35f53a7353
commit 4914135fdf
11 changed files with 67 additions and 33 deletions

View File

@ -988,7 +988,7 @@ class FixtureManager(object):
argnames = getfuncargnames(func, cls=cls) argnames = getfuncargnames(func, cls=cls)
else: else:
argnames = () 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 = argnames
initialnames = tuple(usefixtures) + initialnames initialnames = tuple(usefixtures) + initialnames
fm = node.session._fixturemanager fm = node.session._fixturemanager

View File

@ -35,7 +35,7 @@ class MarkEvaluator(object):
return not hasattr(self, 'exc') return not hasattr(self, 'exc')
def _get_marks(self): 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): def invalidraise(self, exc):
raises = self.get('raises') raises = self.get('raises')

View File

@ -183,20 +183,32 @@ class Node(object):
self.keywords[marker.name] = marker self.keywords[marker.name] = marker
self.own_markers.append(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 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 iterate over all markers of the node
returns sequence of tuples (node, mark) returns sequence of tuples (node, mark)
""" """
for node in reversed(self.listchain()): for node in reversed(self.listchain()):
for mark in node.own_markers: 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): def get_marker(self, name):
""" get a marker object from this node or None if """ get a marker object from this node or None if
@ -206,7 +218,7 @@ class Node(object):
deprecated deprecated
""" """
markers = [x for x in self.iter_markers() if x.name == name] markers = list(self.iter_markers(name=name))
if markers: if markers:
return MarkInfo(markers) return MarkInfo(markers)

View File

@ -118,9 +118,8 @@ def pytest_generate_tests(metafunc):
if hasattr(metafunc.function, attr): if hasattr(metafunc.function, attr):
msg = "{0} has '{1}', spelling should be 'parametrize'" msg = "{0} has '{1}', spelling should be 'parametrize'"
raise MarkerError(msg.format(metafunc.function.__name__, attr)) raise MarkerError(msg.format(metafunc.function.__name__, attr))
for marker in metafunc.definition.iter_markers(): for marker in metafunc.definition.iter_markers(name='parametrize'):
if marker.name == 'parametrize': metafunc.parametrize(*marker.args, **marker.kwargs)
metafunc.parametrize(*marker.args, **marker.kwargs)
def pytest_configure(config): def pytest_configure(config):

View File

@ -64,9 +64,7 @@ def pytest_runtest_setup(item):
item._skipped_by_mark = True item._skipped_by_mark = True
skip(eval_skipif.getexplanation()) skip(eval_skipif.getexplanation())
for skip_info in item.iter_markers(): for skip_info in item.iter_markers(name='skip'):
if skip_info.name != 'skip':
continue
item._skipped_by_mark = True item._skipped_by_mark = True
if 'reason' in skip_info.kwargs: if 'reason' in skip_info.kwargs:
skip(skip_info.kwargs['reason']) skip(skip_info.kwargs['reason'])

View File

@ -60,10 +60,9 @@ def catch_warnings_for_item(item):
for arg in inifilters: for arg in inifilters:
_setoption(warnings, arg) _setoption(warnings, arg)
for mark in item.iter_markers(): for mark in item.iter_markers(name='filterwarnings'):
if mark.name == 'filterwarnings': for arg in mark.args:
for arg in mark.args: warnings._setoption(arg)
warnings._setoption(arg)
yield yield

1
changelog/3446.feature Normal file
View File

@ -0,0 +1 @@
introduce node.get_closest_marker(name, default=None) to support simple marker usage setups.

1
changelog/3459.feature Normal file
View File

@ -0,0 +1 @@
Introduce optional name based filtering for iter_markers

View File

@ -330,7 +330,7 @@ specifies via named environments::
"env(name): mark test to run only on named environment") "env(name): mark test to run only on named environment")
def pytest_runtest_setup(item): 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 envnames:
if item.config.getoption("-E") not in envnames: if item.config.getoption("-E") not in envnames:
pytest.skip("test requires env in %r" % envnames) pytest.skip("test requires env in %r" % envnames)
@ -402,10 +402,9 @@ Below is the config file that will be used in the next examples::
import sys import sys
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
for marker in item.iter_markers(): for marker in item.iter_markers(name='my_marker'):
if marker.name == 'my_marker': print(marker)
print(marker) sys.stdout.flush()
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. 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.
@ -458,10 +457,9 @@ test function. From a conftest file we can read it like this::
import sys import sys
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
for mark in item.iter_markers(): for mark in item.iter_markers(name='glob'):
if mark.name == 'glob': print ("glob args=%s kwargs=%s" %(mark.args, mark.kwargs))
print ("glob args=%s kwargs=%s" %(mark.args, mark.kwargs)) sys.stdout.flush()
sys.stdout.flush()
Let's run this without capturing output and see what we get:: Let's run this without capturing output and see what we get::

View File

@ -274,10 +274,9 @@ Alternatively, you can integrate this functionality with custom markers:
def pytest_collection_modifyitems(session, config, items): def pytest_collection_modifyitems(session, config, items):
for item in items: for item in items:
for marker in item.iter_markers(): for marker in item.iter_markers(name='test_id'):
if marker.name == 'test_id': test_id = marker.args[0]
test_id = marker.args[0] item.user_properties.append(('test_id', test_id))
item.user_properties.append(('test_id', test_id))
And in your tests: And in your tests:

View File

@ -553,7 +553,6 @@ class TestFunctional(object):
self.assert_markers(items, test_foo=('a', 'b'), test_bar=('a',)) self.assert_markers(items, test_foo=('a', 'b'), test_bar=('a',))
@pytest.mark.issue568 @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): def test_mark_should_not_pass_to_siebling_class(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import pytest import pytest
@ -573,8 +572,16 @@ class TestFunctional(object):
""") """)
items, rec = testdir.inline_genitems(p) items, rec = testdir.inline_genitems(p)
base_item, sub_item, sub_item_other = items base_item, sub_item, sub_item_other = items
assert not hasattr(base_item.obj, 'b') print(items, [x.nodeid for x in items])
assert not hasattr(sub_item_other.obj, 'b') # 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): def test_mark_decorator_baseclasses_merged(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
@ -598,6 +605,26 @@ class TestFunctional(object):
self.assert_markers(items, test_foo=('a', 'b', 'c'), self.assert_markers(items, test_foo=('a', 'b', 'c'),
test_bar=('a', 'b', 'd')) 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): def test_mark_with_wrong_marker(self, testdir):
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
import pytest import pytest