diff --git a/_pytest/mark.py b/_pytest/mark.py index 1c22e72dd..842272e3d 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -191,8 +191,7 @@ class MarkDecorator: holder = MarkInfo(self.markname, self.args, self.kwargs) setattr(func, self.markname, holder) else: - holder.kwargs.update(self.kwargs) - holder.args += self.args + holder.add(self.args, self.kwargs) return func kw = self.kwargs.copy() kw.update(kwargs) @@ -208,11 +207,23 @@ class MarkInfo: self.args = args #: keyword argument dictionary, empty if nothing specified self.kwargs = kwargs + self._arglist = [(args, kwargs.copy())] def __repr__(self): return "" % ( self.name, self.args, self.kwargs) + def add(self, args, kwargs): + """ add a MarkInfo with the given args and kwargs. """ + self._arglist.append((args, kwargs)) + self.args += args + self.kwargs.update(kwargs) + + def __iter__(self): + """ yield MarkInfo objects each relating to a marking-call. """ + for args, kwargs in self._arglist: + yield MarkInfo(self.name, args, kwargs) + def pytest_itemcollected(item): if not isinstance(item, pytest.Function): return diff --git a/doc/example/markers.txt b/doc/example/markers.txt index bce273414..d3a71a7d8 100644 --- a/doc/example/markers.txt +++ b/doc/example/markers.txt @@ -25,26 +25,26 @@ You can "mark" a test function with custom metadata like this:: You can then restrict a test run to only run tests marked with ``webtest``:: $ py.test -v -m webtest - =========================== test session starts ============================ - platform darwin -- Python 2.7.1 -- pytest-2.2.1 -- /Users/hpk/venv/1/bin/python + ============================= test session starts ============================== + platform darwin -- Python 2.7.1 -- pytest-2.2.2.dev3 -- /Users/hpk/venv/1/bin/python collecting ... collected 2 items test_server.py:3: test_send_http PASSED - =================== 1 tests deselected by "-m 'webtest'" =================== - ================== 1 passed, 1 deselected in 0.01 seconds ================== + ===================== 1 tests deselected by "-m 'webtest'" ===================== + ==================== 1 passed, 1 deselected in 0.03 seconds ==================== Or the inverse, running all tests except the webtest ones:: $ py.test -v -m "not webtest" - =========================== test session starts ============================ - platform darwin -- Python 2.7.1 -- pytest-2.2.1 -- /Users/hpk/venv/1/bin/python + ============================= test session starts ============================== + platform darwin -- Python 2.7.1 -- pytest-2.2.2.dev3 -- /Users/hpk/venv/1/bin/python collecting ... collected 2 items test_server.py:6: test_something_quick PASSED - ================= 1 tests deselected by "-m 'not webtest'" ================= - ================== 1 passed, 1 deselected in 0.01 seconds ================== + =================== 1 tests deselected by "-m 'not webtest'" =================== + ==================== 1 passed, 1 deselected in 0.03 seconds ==================== Registering markers ------------------------------------- @@ -134,6 +134,7 @@ You can also set a module level marker:: in which case it will be applied to all functions and methods defined in the module. + Using ``-k TEXT`` to select tests ---------------------------------------------------- @@ -141,39 +142,39 @@ You can use the ``-k`` command line option to only run tests with names matching the given argument:: $ py.test -k send_http # running with the above defined examples - =========================== test session starts ============================ - platform darwin -- Python 2.7.1 -- pytest-2.2.1 + ============================= test session starts ============================== + platform darwin -- Python 2.7.1 -- pytest-2.2.2.dev3 collecting ... collected 4 items test_server.py . - =================== 3 tests deselected by '-ksend_http' ==================== - ================== 1 passed, 3 deselected in 0.02 seconds ================== + ===================== 3 tests deselected by '-ksend_http' ====================== + ==================== 1 passed, 3 deselected in 0.06 seconds ==================== And you can also run all tests except the ones that match the keyword:: $ py.test -k-send_http - =========================== test session starts ============================ - platform darwin -- Python 2.7.1 -- pytest-2.2.1 + ============================= test session starts ============================== + platform darwin -- Python 2.7.1 -- pytest-2.2.2.dev3 collecting ... collected 4 items test_mark_classlevel.py .. test_server.py . - =================== 1 tests deselected by '-k-send_http' =================== - ================== 3 passed, 1 deselected in 0.03 seconds ================== + ===================== 1 tests deselected by '-k-send_http' ===================== + ==================== 3 passed, 1 deselected in 0.05 seconds ==================== Or to only select the class:: $ py.test -kTestClass - =========================== test session starts ============================ - platform darwin -- Python 2.7.1 -- pytest-2.2.1 + ============================= test session starts ============================== + platform darwin -- Python 2.7.1 -- pytest-2.2.2.dev3 collecting ... collected 4 items test_mark_classlevel.py .. - =================== 2 tests deselected by '-kTestClass' ==================== - ================== 2 passed, 2 deselected in 0.02 seconds ================== + ===================== 2 tests deselected by '-kTestClass' ====================== + ==================== 2 passed, 2 deselected in 0.04 seconds ==================== .. _`adding a custom marker from a plugin`: @@ -221,24 +222,24 @@ and an example invocations specifying a different environment than what the test needs:: $ py.test -E stage2 - =========================== test session starts ============================ - platform darwin -- Python 2.7.1 -- pytest-2.2.1 + ============================= test session starts ============================== + platform darwin -- Python 2.7.1 -- pytest-2.2.2.dev3 collecting ... collected 1 items test_someenv.py s - ======================== 1 skipped in 0.02 seconds ========================= + ========================== 1 skipped in 0.03 seconds =========================== and here is one that specifies exactly the environment needed:: $ py.test -E stage1 - =========================== test session starts ============================ - platform darwin -- Python 2.7.1 -- pytest-2.2.1 + ============================= test session starts ============================== + platform darwin -- Python 2.7.1 -- pytest-2.2.2.dev3 collecting ... collected 1 items test_someenv.py . - ========================= 1 passed in 0.01 seconds ========================= + =========================== 1 passed in 0.03 seconds =========================== The ``--markers`` option always gives you a list of available markers:: @@ -254,4 +255,43 @@ The ``--markers`` option always gives you a list of available markers:: @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + +Reading markers which were set from multiple places +---------------------------------------------------- + +.. versionadded: 2.2.2 + +If you are heavily using markers in your test suite you may encounter the case where a marker is applied several times to a test function. From plugin +code you can read over all such settings. Example:: + + # content of test_mark_three_times.py + import pytest + pytestmark = pytest.mark.glob("module", x=1) + + @pytest.mark.glob("class", x=2) + class TestClass: + @pytest.mark.glob("function", x=3) + def test_something(self): + pass + +Here we have the marker "glob" applied three times to the same +test function. From a conftest file we can read it like this:: + + # content of conftest.py + + def pytest_runtest_setup(item): + g = getattr(item.obj, 'glob', None) + if g is not None: + for info in g: + print ("glob args=%s kwargs=%s" %(info.args, info.kwargs)) + +Let's run this without capturing output and see what we get:: + + $ py.test -q -s + collecting ... collected 2 items + .. + 2 passed in 0.04 seconds + glob args=('function',) kwargs={'x': 3} + glob args=('module',) kwargs={'x': 1} + glob args=('class',) kwargs={'x': 2} diff --git a/testing/test_mark.py b/testing/test_mark.py index 031001835..2c8b52db5 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -230,6 +230,14 @@ class TestFunctional: assert marker.args == ("pos0", "pos1") assert marker.kwargs == {'x': 3, 'y': 2, 'z': 4} + # test the new __iter__ interface + l = list(marker) + assert len(l) == 3 + assert l[0].args == ("pos0",) + pytest.xfail(reason="needs reordering of parametrize transfermarks") + assert l[1].args == () + assert l[2].args == ("pos1", ) + def test_mark_other(self, testdir): pytest.raises(TypeError, ''' testdir.getitem(""" @@ -259,6 +267,23 @@ class TestFunctional: "keyword: *hello*" ]) + def test_merging_markers_two_functions(self, testdir): + p = testdir.makepyfile(""" + import pytest + @pytest.mark.hello("pos1", z=4) + @pytest.mark.hello("pos0", z=3) + def test_func(self): + pass + """) + items, rec = testdir.inline_genitems(p) + item, = items + keywords = item.keywords + marker = keywords['hello'] + l = list(marker) + assert len(l) == 2 + assert l[0].args == ("pos0",) + assert l[1].args == ("pos1",) + class TestKeywordSelection: def test_select_simple(self, testdir):