diff --git a/_pytest/deprecated.py b/_pytest/deprecated.py index a0eec0e7d..be4a5a080 100644 --- a/_pytest/deprecated.py +++ b/_pytest/deprecated.py @@ -32,7 +32,8 @@ RESULT_LOG = ( ) 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.find_markers to iterate over markers correctly" ) MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning( diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 5c6a4a230..457aa9d4f 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -5,6 +5,7 @@ import inspect import sys import warnings from collections import OrderedDict, deque, defaultdict +from more_itertools import flatten import attr import py @@ -982,10 +983,10 @@ class FixtureManager(object): argnames = getfuncargnames(func, cls=cls) else: argnames = () - usefixtures = getattr(func, "usefixtures", None) + usefixtures = flatten(uf_mark.args for uf_mark in node.find_markers("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) @@ -1067,6 +1068,8 @@ class FixtureManager(object): fixturedef = faclist[-1] if fixturedef.params is not None: parametrize_func = getattr(metafunc.function, 'parametrize', None) + 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 diff --git a/_pytest/mark/structures.py b/_pytest/mark/structures.py index fe0e1983d..b42a66551 100644 --- a/_pytest/mark/structures.py +++ b/_pytest/mark/structures.py @@ -4,7 +4,7 @@ from operator import attrgetter 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 @@ -260,10 +260,10 @@ 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) class MarkInfo(object): @@ -274,9 +274,9 @@ class MarkInfo(object): self.combined = mark self._marks = [mark] - 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) def __repr__(self): return "".format(self.combined) diff --git a/_pytest/python.py b/_pytest/python.py index f7d4c601c..843d7f534 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -117,11 +117,7 @@ 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)) - try: - markers = metafunc.function.parametrize - except AttributeError: - return - for marker in markers: + for marker in metafunc.definition.find_markers('parametrize'): metafunc.parametrize(*marker.args, **marker.kwargs) @@ -212,6 +208,7 @@ class PyobjContext(object): class PyobjMixin(PyobjContext): + _ALLOW_MARKERS = True def __init__(self, *k, **kw): super(PyobjMixin, self).__init__(*k, **kw) @@ -221,8 +218,9 @@ class PyobjMixin(PyobjContext): 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)) + # XXX evil hack + if self._ALLOW_MARKERS: + self._markers.update(get_unpacked_marks(self.obj)) return obj def fset(self, value): @@ -370,8 +368,13 @@ class PyCollector(PyobjMixin, nodes.Collector): transfer_markers(funcobj, cls, module) fm = self.session._fixturemanager fixtureinfo = fm.getfixtureinfo(self, funcobj, cls) - metafunc = Metafunc(funcobj, fixtureinfo, self.config, - cls=cls, module=module) + + definition = FunctionDefinition( + name=name, + parent=self, + callobj=funcobj, + ) + metafunc = Metafunc(definition, fixtureinfo, self.config, cls=cls, module=module) methods = [] if hasattr(module, "pytest_generate_tests"): methods.append(module.pytest_generate_tests) @@ -530,6 +533,8 @@ class Class(PyCollector): class Instance(PyCollector): + _ALLOW_MARKERS = False # hack, destroy later + def _getobj(self): return self.parent.obj() @@ -729,15 +734,17 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): test function is defined. """ - def __init__(self, function, fixtureinfo, config, cls=None, module=None): + def __init__(self, definition, fixtureinfo, config, cls=None, module=None): #: access to the :class:`_pytest.config.Config` object for the test session + assert isinstance(definition, FunctionDefinition) + self.definition = definition self.config = config #: the module object where the test function is defined in. self.module = module #: underlying python test function - self.function = function + self.function = definition.obj #: set of fixture names required by the test function self.fixturenames = fixtureinfo.names_closure @@ -1189,3 +1196,15 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr): def setup(self): super(Function, self).setup() fixtures.fillfixtures(self) + + +class FunctionDefinition(Function): + """ + internal hack until we get actual definition nodes instead of the + crappy metafunc hack + """ + + def runtest(self): + raise RuntimeError("function definitions are not supposed to be used") + + setup = runtest diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 48b837def..318c8795e 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -2,7 +2,6 @@ from __future__ import absolute_import, division, print_function from _pytest.config import hookimpl -from _pytest.mark import MarkInfo, MarkDecorator from _pytest.mark.evaluate import MarkEvaluator from _pytest.outcomes import fail, skip, xfail @@ -60,15 +59,12 @@ def pytest_configure(config): def pytest_runtest_setup(item): # Check if skip or skipif are specified as pytest marks item._skipped_by_mark = False - skipif_info = item.keywords.get('skipif') - if isinstance(skipif_info, (MarkInfo, MarkDecorator)): - eval_skipif = MarkEvaluator(item, 'skipif') - if eval_skipif.istrue(): - item._skipped_by_mark = True - skip(eval_skipif.getexplanation()) + eval_skipif = MarkEvaluator(item, 'skipif') + if eval_skipif.istrue(): + item._skipped_by_mark = True + skip(eval_skipif.getexplanation()) - skip_info = item.keywords.get('skip') - if isinstance(skip_info, (MarkInfo, MarkDecorator)): + for skip_info in item.find_markers('skip'): item._skipped_by_mark = True if 'reason' in skip_info.kwargs: skip(skip_info.kwargs['reason']) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 59c5266cb..c558ea3cf 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -1781,6 +1781,8 @@ class TestAutouseManagement(object): import pytest values = [] def pytest_generate_tests(metafunc): + if metafunc.cls is None: + assert metafunc.function is test_finish if metafunc.cls is not None: metafunc.parametrize("item", [1,2], scope="class") class TestClass(object): @@ -1798,7 +1800,7 @@ class TestAutouseManagement(object): assert values == ["setup-1", "step1-1", "step2-1", "teardown-1", "setup-2", "step1-2", "step2-2", "teardown-2",] """) - reprec = testdir.inline_run() + reprec = testdir.inline_run('-s') reprec.assertoutcome(passed=5) def test_ordering_autouse_before_explicit(self, testdir): diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index f2732ef3b..b65a42e09 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -26,11 +26,17 @@ class TestMetafunc(object): names = fixtures.getfuncargnames(func) fixtureinfo = FixtureInfo(names) - return python.Metafunc(func, fixtureinfo, config) + definition = python.FunctionDefinition( + name=func.__name__, + parent=None, + callobj=func, + ) + return python.Metafunc(definition, fixtureinfo, config) def test_no_funcargs(self, testdir): def function(): pass + metafunc = self.Metafunc(function) assert not metafunc.fixturenames repr(metafunc._calls) diff --git a/testing/test_mark.py b/testing/test_mark.py index 9f4a7fc88..42c5d8bc5 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -8,6 +8,8 @@ from _pytest.mark import ( EMPTY_PARAMETERSET_OPTION, ) +ignore_markinfo = pytest.mark.filterwarnings('ignore:MarkInfo objects:_pytest.deprecated.RemovedInPytest4Warning') + class TestMark(object): def test_markinfo_repr(self): @@ -51,6 +53,7 @@ class TestMark(object): mark.hello(f) assert f.hello + @ignore_markinfo def test_pytest_mark_keywords(self): mark = Mark() @@ -62,6 +65,7 @@ class TestMark(object): assert f.world.kwargs['x'] == 3 assert f.world.kwargs['y'] == 4 + @ignore_markinfo def test_apply_multiple_and_merge(self): mark = Mark() @@ -78,6 +82,7 @@ class TestMark(object): assert f.world.kwargs['y'] == 1 assert len(f.world.args) == 0 + @ignore_markinfo def test_pytest_mark_positional(self): mark = Mark() @@ -88,6 +93,7 @@ class TestMark(object): assert f.world.args[0] == "hello" mark.world("world")(f) + @ignore_markinfo def test_pytest_mark_positional_func_and_keyword(self): mark = Mark() @@ -103,6 +109,7 @@ class TestMark(object): assert g.world.args[0] is f assert g.world.kwargs["omega"] == "hello" + @ignore_markinfo def test_pytest_mark_reuse(self): mark = Mark() @@ -484,6 +491,7 @@ class TestFunctional(object): assert 'hello' in keywords assert 'world' in keywords + @ignore_markinfo def test_merging_markers(self, testdir): p = testdir.makepyfile(""" import pytest @@ -621,6 +629,7 @@ class TestFunctional(object): "keyword: *hello*" ]) + @ignore_markinfo def test_merging_markers_two_functions(self, testdir): p = testdir.makepyfile(""" import pytest