more fixes regarding marking, in particular plugins should use add_marker/get_marker now.
This commit is contained in:
		
							parent
							
								
									9fdfa155fb
								
							
						
					
					
						commit
						2248a31a44
					
				
							
								
								
									
										10
									
								
								CHANGELOG
								
								
								
								
							
							
						
						
									
										10
									
								
								CHANGELOG
								
								
								
								
							|  | @ -11,6 +11,16 @@ Changes between 2.4.1 and 2.4.2 | |||
| - avoid tmpdir fixture to create too long filenames especially | ||||
|   when parametrization is used (issue354) | ||||
| 
 | ||||
| - fix pytest-pep8 and pytest-flakes / pytest interactions | ||||
|   (collection names in mark plugin was assuming an item always | ||||
|   has a function which is not true for those plugins etc.) | ||||
|   Thanks Andi Zeidler. | ||||
| 
 | ||||
| - introduce node.get_marker/node.add_marker API for plugins | ||||
|   like pytest-pep8 and pytest-flakes to avoid the messy | ||||
|   details of the node.keywords  pseudo-dicts.  Adapated | ||||
|   docs. | ||||
| 
 | ||||
| Changes between 2.4.0 and 2.4.1 | ||||
| ----------------------------------- | ||||
| 
 | ||||
|  |  | |||
|  | @ -321,6 +321,27 @@ class Node(object): | |||
|         chain.reverse() | ||||
|         return chain | ||||
| 
 | ||||
|     def add_marker(self, marker): | ||||
|         """ dynamically add a marker object to the node. | ||||
| 
 | ||||
|         ``marker`` can be a string or pytest.mark.* instance. | ||||
|         """ | ||||
|         from _pytest.mark import MarkDecorator | ||||
|         if isinstance(marker, py.builtin._basestring): | ||||
|             marker = MarkDecorator(marker) | ||||
|         elif not isinstance(marker, MarkDecorator): | ||||
|             raise ValueError("is not a string or pytest.mark.* Marker") | ||||
|         self.keywords[marker.name] = marker | ||||
| 
 | ||||
|     def get_marker(self, name): | ||||
|         """ get a marker object from this node or None if | ||||
|         the node doesn't have a marker with that name. """ | ||||
|         val = self.keywords.get(name, None) | ||||
|         if val is not None: | ||||
|             from _pytest.mark import MarkInfo, MarkDecorator | ||||
|             if isinstance(val, (MarkDecorator, MarkInfo)): | ||||
|                 return val | ||||
| 
 | ||||
|     def listextrakeywords(self): | ||||
|         """ Return a set of all extra keywords in self and any parents.""" | ||||
|         extra_keywords = set() | ||||
|  |  | |||
|  | @ -81,11 +81,8 @@ def pytest_collection_modifyitems(items, config): | |||
| 
 | ||||
| 
 | ||||
| class MarkMapping: | ||||
|     """Provides a local mapping for markers. | ||||
|     Only the marker names from the given :class:`NodeKeywords` will be mapped, | ||||
|     so the names are taken only from :class:`MarkInfo` or | ||||
|     :class:`MarkDecorator` items. | ||||
|     """ | ||||
|     """Provides a local mapping for markers where item access | ||||
|     resolves to True if the marker is present. """ | ||||
|     def __init__(self, keywords): | ||||
|         mymarks = set() | ||||
|         for key, value in keywords.items(): | ||||
|  | @ -93,8 +90,8 @@ class MarkMapping: | |||
|                 mymarks.add(key) | ||||
|         self._mymarks = mymarks | ||||
| 
 | ||||
|     def __getitem__(self, markname): | ||||
|         return markname in self._mymarks | ||||
|     def __getitem__(self, name): | ||||
|         return name in self._mymarks | ||||
| 
 | ||||
| 
 | ||||
| class KeywordMapping: | ||||
|  | @ -202,13 +199,17 @@ class MarkDecorator: | |||
|             pass | ||||
|     """ | ||||
|     def __init__(self, name, args=None, kwargs=None): | ||||
|         self.markname = name | ||||
|         self.name = name | ||||
|         self.args = args or () | ||||
|         self.kwargs = kwargs or {} | ||||
| 
 | ||||
|     @property | ||||
|     def markname(self): | ||||
|         return self.name # for backward-compat (2.4.1 had this attr) | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         d = self.__dict__.copy() | ||||
|         name = d.pop('markname') | ||||
|         name = d.pop('name') | ||||
|         return "<MarkDecorator %r %r>" % (name, d) | ||||
| 
 | ||||
|     def __call__(self, *args, **kwargs): | ||||
|  | @ -228,19 +229,19 @@ class MarkDecorator: | |||
|                     else: | ||||
|                         func.pytestmark = [self] | ||||
|                 else: | ||||
|                     holder = getattr(func, self.markname, None) | ||||
|                     holder = getattr(func, self.name, None) | ||||
|                     if holder is None: | ||||
|                         holder = MarkInfo( | ||||
|                             self.markname, self.args, self.kwargs | ||||
|                             self.name, self.args, self.kwargs | ||||
|                         ) | ||||
|                         setattr(func, self.markname, holder) | ||||
|                         setattr(func, self.name, holder) | ||||
|                     else: | ||||
|                         holder.add(self.args, self.kwargs) | ||||
|                 return func | ||||
|         kw = self.kwargs.copy() | ||||
|         kw.update(kwargs) | ||||
|         args = self.args + args | ||||
|         return self.__class__(self.markname, args=args, kwargs=kw) | ||||
|         return self.__class__(self.name, args=args, kwargs=kw) | ||||
| 
 | ||||
| 
 | ||||
| class MarkInfo: | ||||
|  |  | |||
|  | @ -235,7 +235,7 @@ specifies via named environments:: | |||
|             "env(name): mark test to run only on named environment") | ||||
| 
 | ||||
|     def pytest_runtest_setup(item): | ||||
|         envmarker = item.keywords.get("env", None) | ||||
|         envmarker = item.get_marker("env") | ||||
|         if envmarker is not None: | ||||
|             envname = envmarker.args[0] | ||||
|             if envname != item.config.getoption("-E"): | ||||
|  | @ -318,7 +318,7 @@ test function.  From a conftest file we can read it like this:: | |||
|     import sys | ||||
| 
 | ||||
|     def pytest_runtest_setup(item): | ||||
|         g = item.keywords.get("glob", None) | ||||
|         g = item.get_marker("glob") | ||||
|         if g is not None: | ||||
|             for info in g: | ||||
|                 print ("glob args=%s kwargs=%s" %(info.args, info.kwargs)) | ||||
|  | @ -353,7 +353,7 @@ for your particular platform, you could use the following plugin:: | |||
|     def pytest_runtest_setup(item): | ||||
|         if isinstance(item, item.Function): | ||||
|             plat = sys.platform | ||||
|             if plat not in item.keywords: | ||||
|             if not item.get_marker(plat): | ||||
|                 if ALL.intersection(item.keywords): | ||||
|                     pytest.skip("cannot run on platform %s" %(plat)) | ||||
| 
 | ||||
|  | @ -439,9 +439,9 @@ We want to dynamically define two markers and can do it in a | |||
|     def pytest_collection_modifyitems(items): | ||||
|         for item in items: | ||||
|             if "interface" in item.nodeid: | ||||
|                 item.keywords["interface"] = pytest.mark.interface | ||||
|                 item.add_marker(pytest.mark.interface) | ||||
|             elif "event" in item.nodeid: | ||||
|                 item.keywords["event"] = pytest.mark.event | ||||
|                 item.add_marker(pytest.mark.event) | ||||
| 
 | ||||
| We can now use the ``-m option`` to select one set:: | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,16 @@ | |||
| #!/bin/bash | ||||
| 
 | ||||
| # this assumes plugins are installed as sister directories | ||||
| 
 | ||||
| set -e | ||||
| cd ../pytest-pep8 | ||||
| py.test | ||||
| cd ../pytest-instafail | ||||
| py.test  | ||||
| cd ../pytest-cache | ||||
| py.test | ||||
| #cd ../pytest-cov | ||||
| #py.test | ||||
| cd ../pytest-xdist | ||||
| py.test | ||||
| 
 | ||||
|  | @ -180,6 +180,7 @@ def test_keyword_option_custom(spec, testdir): | |||
|     assert len(passed) == len(passed_result) | ||||
|     assert list(passed) == list(passed_result) | ||||
| 
 | ||||
| 
 | ||||
| class TestFunctional: | ||||
| 
 | ||||
|     def test_mark_per_function(self, testdir): | ||||
|  | @ -362,7 +363,6 @@ class TestFunctional: | |||
|         deselected_tests = dlist[0].items | ||||
|         assert len(deselected_tests) == 2 | ||||
| 
 | ||||
| 
 | ||||
|     def test_keywords_at_node_level(self, testdir): | ||||
|         p = testdir.makepyfile(""" | ||||
|             import pytest | ||||
|  | @ -383,6 +383,30 @@ class TestFunctional: | |||
|         reprec = testdir.inline_run() | ||||
|         reprec.assertoutcome(passed=1) | ||||
| 
 | ||||
|     def test_keyword_added_for_session(self, testdir): | ||||
|         testdir.makeconftest(""" | ||||
|             import pytest | ||||
|             def pytest_collection_modifyitems(session): | ||||
|                 session.add_marker("mark1") | ||||
|                 session.add_marker(pytest.mark.mark2) | ||||
|                 session.add_marker(pytest.mark.mark3) | ||||
|                 pytest.raises(ValueError, lambda: | ||||
|                         session.add_marker(10)) | ||||
|         """) | ||||
|         testdir.makepyfile(""" | ||||
|             def test_some(request): | ||||
|                 assert "mark1" in request.keywords | ||||
|                 assert "mark2" in request.keywords | ||||
|                 assert "mark3" in request.keywords | ||||
|                 assert 10 not in request.keywords | ||||
|                 marker = request.node.get_marker("mark1") | ||||
|                 assert marker.name == "mark1" | ||||
|                 assert marker.args == () | ||||
|                 assert marker.kwargs == {} | ||||
|         """) | ||||
|         reprec = testdir.inline_run("-m", "mark1") | ||||
|         reprec.assertoutcome(passed=1) | ||||
| 
 | ||||
| class TestKeywordSelection: | ||||
|     def test_select_simple(self, testdir): | ||||
|         file_test = testdir.makepyfile(""" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue