Commits

holger krekel committed df67c0e

remove .markers attribute which was added in development and after 2.2.4
so never released. Rather extend keywords to also exist on nodes. Assigning
to node.keywords will make the value appear on all subchildren's
keywords.

Comments (0)

Files changed (9)

_pytest/__init__.py

 #
-__version__ = '2.3.0.dev27'
+__version__ = '2.3.0.dev28'
 import pytest, _pytest
 import inspect
 import os, sys, imp
+try:
+    from collections import MutableMapping as MappingMixin
+except ImportError:
+    from UserDict import DictMixin as MappingMixin
 
 from _pytest.mark import MarkInfo
 
 
     return property(fget)
 
+class NodeKeywords(MappingMixin):
+    def __init__(self, node):
+        parent = node.parent
+        bases = parent and (parent.keywords._markers,) or ()
+        self._markers = type("dynmarker", bases, {node.name: True})
+
+    def __getitem__(self, key):
+        try:
+            return getattr(self._markers, key)
+        except AttributeError:
+            raise KeyError(key)
+
+    def __setitem__(self, key, value):
+        setattr(self._markers, key, value)
+
+    def __delitem__(self, key):
+        delattr(self._markers, key)
+
+    def __iter__(self):
+        return iter(self.keys())
+
+    def __len__(self):
+        return len(self.keys())
+
+    def keys(self):
+        return dir(self._markers)
 
 class Node(object):
     """ base class for Collector and Item the test collection tree.
         #: fspath sensitive hook proxy used to call pytest hooks
         self.ihook = self.session.gethookproxy(self.fspath)
 
-        bases = parent and (parent.markers,) or ()
-
-        #: marker class with markers from all scopes accessible as attributes
-        self.markers = type("dynmarker", bases, {self.name: True})
+        #: keywords/markers collected from all scopes
+        self.keywords = NodeKeywords(self)
 
         #self.extrainit()
 
-    @property
-    def keywords(self):
-        """ dictionary of Keywords / markers on this node. """
-        return vars(self.markers)
-
-    def applymarker(self, marker):
-        """ Apply a marker to this item.  This method is
-        useful if you have several parametrized function
-        and want to mark a single one of them.
-
-        :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
-            created by a call to ``py.test.mark.NAME(...)``.
-        """
-        if not isinstance(marker, pytest.mark.XYZ.__class__):
-            raise ValueError("%r is not a py.test.mark.* object")
-        setattr(self.markers, marker.markname, marker)
-
     #def extrainit(self):
     #    """"extra initialization after Node is initialized.  Implemented
     #    by some subclasses. """
     if not keywordexpr:
         return
 
-    itemkeywords = getkeywords(colitem)
+    itemkeywords = colitem.keywords
     for key in filter(None, keywordexpr.split()):
         eor = key[:1] == '-'
         if eor:
         if not (eor ^ matchonekeyword(key, itemkeywords)):
             return True
 
-def getkeywords(node):
-    keywords = {}
-    while node is not None:
-        keywords.update(node.keywords)
-        node = node.parent
-    return keywords
-
-
 def matchonekeyword(key, itemkeywords):
     for elem in key.split("."):
         for kw in itemkeywords:

_pytest/python.py

             self.obj = callobj
 
         for name, val in (py.builtin._getfuncdict(self.obj) or {}).items():
-            setattr(self.markers, name, val)
+            self.keywords[name] = val
         if keywords:
             for name, val in keywords.items():
-                setattr(self.markers, name, val)
+                self.keywords[name] = val
 
         fm = self.session._fixturemanager
         self._fixtureinfo = fi = fm.getfixtureinfo(self.parent,
 
     @property
     def keywords(self):
-        """ (deprecated, use node.markers class) dictionary of markers. """
+        """ keywords/markers dictionary for the underlying node. """
         return self._pyfuncitem.keywords
 
     @property
         :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
             created by a call to ``py.test.mark.NAME(...)``.
         """
-        self.node.applymarker(marker)
+        try:
+            self.node.keywords[marker.markname] = marker
+        except AttributeError:
+            raise ValueError(marker)
 
     def raiseerror(self, msg):
         """ raise a FixtureLookupError with the given message. """
 #
 # The full version, including alpha/beta/rc tags.
 # The short X.Y version.
-version = release = "2.3.0.dev26"
+version = release = "2.3.0.dev28"
 
 import sys, os
 

doc/en/example/markers.txt

 
     $ py.test -v -m webtest
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27 -- /home/hpk/p/pytest/.tox/regen/bin/python
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27 -- /home/hpk/venv/1/bin/python
+    cachedir: /tmp/doc-exec-66/.cache
+    plugins: xdist, bugzilla, cache, cli, pep8, oejskit, timeout, cov
     collecting ... collected 2 items
     
     test_server.py:3: test_send_http PASSED
     
     $ py.test -v -m "not webtest"
     =========================== test session starts ============================
-    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27 -- /home/hpk/p/pytest/.tox/regen/bin/python
+    platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27 -- /home/hpk/venv/1/bin/python
+    cachedir: /tmp/doc-exec-66/.cache
+    plugins: xdist, bugzilla, cache, cli, pep8, oejskit, timeout, cov
     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.00 seconds ==================
+    ================== 1 passed, 1 deselected in 0.01 seconds ==================
 
 Registering markers
 -------------------------------------
     $ py.test --markers
     @pytest.mark.webtest: mark a test as a webtest.
     
+    @pytest.mark.timeout(timeout, method=None): Set a timeout and timeout method on just one test item.  The first argument, *timeout*, is the timeout in seconds while the keyword, *method*, takes the same values as the --timeout_method option.
+    
     @pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. 
     
     @pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied.
     $ py.test -k send_http  # running with the above defined examples
     =========================== test session starts ============================
     platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27
+    plugins: xdist, bugzilla, cache, cli, pep8, oejskit, timeout, cov
     collected 4 items
     
     test_server.py .
     
     =================== 3 tests deselected by '-ksend_http' ====================
-    ================== 1 passed, 3 deselected in 0.01 seconds ==================
+    ================== 1 passed, 3 deselected in 0.02 seconds ==================
 
 And you can also run all tests except the ones that match the keyword::
 
     $ py.test -k-send_http
     =========================== test session starts ============================
     platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27
+    plugins: xdist, bugzilla, cache, cli, pep8, oejskit, timeout, cov
     collected 4 items
     
     test_mark_classlevel.py ..
     test_server.py .
     
     =================== 1 tests deselected by '-k-send_http' ===================
-    ================== 3 passed, 1 deselected in 0.01 seconds ==================
+    ================== 3 passed, 1 deselected in 0.02 seconds ==================
 
 Or to only select the class::
 
     $ py.test -kTestClass
     =========================== test session starts ============================
     platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27
+    plugins: xdist, bugzilla, cache, cli, pep8, oejskit, timeout, cov
     collected 4 items
     
     test_mark_classlevel.py ..
     
     =================== 2 tests deselected by '-kTestClass' ====================
-    ================== 2 passed, 2 deselected in 0.01 seconds ==================
+    ================== 2 passed, 2 deselected in 0.02 seconds ==================
 
 .. _`adding a custom marker from a plugin`:
 
             "env(name): mark test to run only on named environment")
 
     def pytest_runtest_setup(item):
-        envmarker = getattr(item.markers, 'env', None)
+        envmarker = item.keywords.get("env", None)
         if envmarker is not None:
             envname = envmarker.args[0]
             if envname != item.config.option.env:
     $ py.test -E stage2
     =========================== test session starts ============================
     platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27
+    plugins: xdist, bugzilla, cache, cli, pep8, oejskit, timeout, cov
     collected 1 items
     
     test_someenv.py s
     
-    ======================== 1 skipped in 0.00 seconds =========================
+    ======================== 1 skipped in 0.01 seconds =========================
   
 and here is one that specifies exactly the environment needed::
 
     $ py.test -E stage1
     =========================== test session starts ============================
     platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27
+    plugins: xdist, bugzilla, cache, cli, pep8, oejskit, timeout, cov
     collected 1 items
     
     test_someenv.py .
     
-    ========================= 1 passed in 0.00 seconds =========================
+    ========================= 1 passed in 0.01 seconds =========================
 
 The ``--markers`` option always gives you a list of available markers::
 
     $ py.test --markers
     @pytest.mark.env(name): mark test to run only on named environment
     
+    @pytest.mark.timeout(timeout, method=None): Set a timeout and timeout method on just one test item.  The first argument, *timeout*, is the timeout in seconds while the keyword, *method*, takes the same values as the --timeout_method option.
+    
     @pytest.mark.skipif(*conditions): skip the given test function if evaluation of all conditions has a True value.  Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. 
     
     @pytest.mark.xfail(*conditions, reason=None, run=True): mark the the test function as an expected failure. Optionally specify a reason and run=False if you don't even want to execute the test function. Any positional condition strings will be evaluated (like with skipif) and if one is False the marker will not be applied.
     import sys
 
     def pytest_runtest_setup(item):
-        g = getattr(item.markers, "glob", None)
+        g = item.keywords.get("glob", None)
         if g is not None:
             for info in g:
                 print ("glob args=%s kwargs=%s" %(info.args, info.kwargs))
     def pytest_runtest_setup(item):
         if isinstance(item, item.Function):
             plat = sys.platform
-            if not hasattr(item.markers, plat):
-                if ALL.intersection(set(item.obj.__dict__)):
+            if plat not in item.keywords:
+                if ALL.intersection(item.keywords):
                     pytest.skip("cannot run on platform %s" %(plat))
 
 then tests will be skipped if they were specified for a different platform.
     $ py.test -rs # this option reports skip reasons
     =========================== test session starts ============================
     platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27
+    plugins: xdist, bugzilla, cache, cli, pep8, oejskit, timeout, cov
     collected 4 items
     
     test_plat.py s.s.
     ========================= short test summary info ==========================
-    SKIP [2] /tmp/doc-exec-54/conftest.py:12: cannot run on platform linux2
+    SKIP [2] /tmp/doc-exec-66/conftest.py:12: cannot run on platform linux2
     
-    =================== 2 passed, 2 skipped in 0.01 seconds ====================
+    =================== 2 passed, 2 skipped in 0.02 seconds ====================
 
 Note that if you specify a platform via the marker-command line option like this::
 
     $ py.test -m linux2
     =========================== test session starts ============================
     platform linux2 -- Python 2.7.3 -- pytest-2.3.0.dev27
+    plugins: xdist, bugzilla, cache, cli, pep8, oejskit, timeout, cov
     collected 4 items
     
     test_plat.py .
         name='pytest',
         description='py.test: simple powerful testing with Python',
         long_description = long_description,
-        version='2.3.0.dev27',
+        version='2.3.0.dev28',
         url='http://pytest.org',
         license='MIT license',
         platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

testing/test_mark.py

             check(keyword, 'test_one')
         check('TestClass.test', 'test_method_one')
 
-    def test_select_extra_keywords(self, testdir):
+    @pytest.mark.parametrize("keyword", [
+        'xxx', 'xxx test_2', 'TestClass', 'xxx -test_1',
+        'TestClass test_2', 'xxx TestClass test_2'])
+    def test_select_extra_keywords(self, testdir, keyword):
         p = testdir.makepyfile(test_select="""
             def test_1():
                 pass
             def pytest_pycollect_makeitem(__multicall__, name):
                 if name == "TestClass":
                     item = __multicall__.execute()
-                    item.markers.xxx = True
+                    item.keywords["xxx"] = True
                     return item
         """)
-        for keyword in ('xxx', 'xxx test_2', 'TestClass', 'xxx -test_1',
-                        'TestClass test_2', 'xxx TestClass test_2',):
-            reprec = testdir.inline_run(p.dirpath(), '-s', '-k', keyword)
-            py.builtin.print_("keyword", repr(keyword))
-            passed, skipped, failed = reprec.listoutcomes()
-            assert len(passed) == 1
-            assert passed[0].nodeid.endswith("test_2")
-            dlist = reprec.getcalls("pytest_deselected")
-            assert len(dlist) == 1
-            assert dlist[0].items[0].name == 'test_1'
+        reprec = testdir.inline_run(p.dirpath(), '-s', '-k', keyword)
+        py.builtin.print_("keyword", repr(keyword))
+        passed, skipped, failed = reprec.listoutcomes()
+        assert len(passed) == 1
+        assert passed[0].nodeid.endswith("test_2")
+        dlist = reprec.getcalls("pytest_deselected")
+        assert len(dlist) == 1
+        assert dlist[0].items[0].name == 'test_1'
 
     def test_select_starton(self, testdir):
         threepass = testdir.makepyfile(test_threepass="""

testing/test_python.py

         assert 'skipif' in item1.keywords
         pytest.raises(ValueError, "req1.applymarker(42)")
 
-    def test_accessmarker_function(self, testdir):
+    def test_accesskeywords(self, testdir):
         testdir.makepyfile("""
             import pytest
             @pytest.fixture()
-            def markers(request):
-                return request.node.markers
+            def keywords(request):
+                return request.keywords
             @pytest.mark.XYZ
-            def test_function(markers):
-                assert markers.XYZ is not None
-                assert not hasattr(markers, "abc")
+            def test_function(keywords):
+                assert keywords["XYZ"]
+                assert "abc" not in keywords
         """)
         reprec = testdir.inline_run()
         reprec.assertoutcome(passed=1)
         testdir.makeconftest("""
             import pytest
             @pytest.fixture()
-            def markers(request):
-                return request.node.markers
+            def keywords(request):
+                return request.keywords
 
             @pytest.fixture(scope="class", autouse=True)
             def marking(request):
         """)
         testdir.makepyfile("""
             import pytest
-            def test_fun1(markers):
-                assert markers.XYZ is not None
-                assert not hasattr(markers, "abc")
-            def test_fun2(markers):
-                assert markers.XYZ is not None
-                assert not hasattr(markers, "abc")
+            def test_fun1(keywords):
+                assert keywords["XYZ"] is not None
+                assert "abc" not in keywords
+            def test_fun2(keywords):
+                assert keywords["XYZ"] is not None
+                assert "abc" not in keywords
         """)
         reprec = testdir.inline_run()
         reprec.assertoutcome(passed=2)