98 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			98 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
| """
 | |
| this is a place where we put datastructures used by legacy apis
 | |
| we hope ot remove
 | |
| """
 | |
| import attr
 | |
| import keyword
 | |
| 
 | |
| from . import MarkInfo, MarkDecorator
 | |
| 
 | |
| from _pytest.config import UsageError
 | |
| 
 | |
| 
 | |
| @attr.s
 | |
| class MarkMapping(object):
 | |
|     """Provides a local mapping for markers where item access
 | |
|     resolves to True if the marker is present. """
 | |
| 
 | |
|     own_mark_names = attr.ib()
 | |
| 
 | |
|     @classmethod
 | |
|     def from_keywords(cls, keywords):
 | |
|         mark_names = set()
 | |
|         for key, value in keywords.items():
 | |
|             if isinstance(value, MarkInfo) or isinstance(value, MarkDecorator):
 | |
|                 mark_names.add(key)
 | |
|         return cls(mark_names)
 | |
| 
 | |
|     def __getitem__(self, name):
 | |
|         return name in self.own_mark_names
 | |
| 
 | |
| 
 | |
| class KeywordMapping(object):
 | |
|     """Provides a local mapping for keywords.
 | |
|     Given a list of names, map any substring of one of these names to True.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, names):
 | |
|         self._names = names
 | |
| 
 | |
|     @classmethod
 | |
|     def from_item(cls, item):
 | |
|         mapped_names = set()
 | |
| 
 | |
|         # Add the names of the current item and any parent items
 | |
|         import pytest
 | |
|         for item in item.listchain():
 | |
|             if not isinstance(item, pytest.Instance):
 | |
|                 mapped_names.add(item.name)
 | |
| 
 | |
|         # Add the names added as extra keywords to current or parent items
 | |
|         for name in item.listextrakeywords():
 | |
|             mapped_names.add(name)
 | |
| 
 | |
|         # Add the names attached to the current function through direct assignment
 | |
|         if hasattr(item, 'function'):
 | |
|             for name in item.function.__dict__:
 | |
|                 mapped_names.add(name)
 | |
| 
 | |
|         return cls(mapped_names)
 | |
| 
 | |
|     def __getitem__(self, subname):
 | |
|         for name in self._names:
 | |
|             if subname in name:
 | |
|                 return True
 | |
|         return False
 | |
| 
 | |
| 
 | |
| python_keywords_allowed_list = ["or", "and", "not"]
 | |
| 
 | |
| 
 | |
| def matchmark(colitem, markexpr):
 | |
|     """Tries to match on any marker names, attached to the given colitem."""
 | |
|     return eval(markexpr, {}, MarkMapping.from_keywords(colitem.keywords))
 | |
| 
 | |
| 
 | |
| def matchkeyword(colitem, keywordexpr):
 | |
|     """Tries to match given keyword expression to given collector item.
 | |
| 
 | |
|     Will match on the name of colitem, including the names of its parents.
 | |
|     Only matches names of items which are either a :class:`Class` or a
 | |
|     :class:`Function`.
 | |
|     Additionally, matches on names in the 'extra_keyword_matches' set of
 | |
|     any item, as well as names directly assigned to test functions.
 | |
|     """
 | |
|     mapping = KeywordMapping.from_item(colitem)
 | |
|     if " " not in keywordexpr:
 | |
|         # special case to allow for simple "-k pass" and "-k 1.3"
 | |
|         return mapping[keywordexpr]
 | |
|     elif keywordexpr.startswith("not ") and " " not in keywordexpr[4:]:
 | |
|         return not mapping[keywordexpr[4:]]
 | |
|     for kwd in keywordexpr.split():
 | |
|         if keyword.iskeyword(kwd) and kwd not in python_keywords_allowed_list:
 | |
|             raise UsageError("Python keyword '{}' not accepted in expressions passed to '-k'".format(kwd))
 | |
|     try:
 | |
|         return eval(keywordexpr, {}, mapping)
 | |
|     except SyntaxError:
 | |
|         raise UsageError("Wrong expression passed to '-k': {}".format(keywordexpr))
 |