1# pytest custom collection adapter for legacy pyyaml unit tests/data files; surfaces each 2# legacy test case as a pyyaml item 3 4import os 5import pathlib 6import pytest 7import warnings 8 9from test_appliance import find_test_filenames, DATA 10 11try: 12 from yaml import _yaml 13 HAS_LIBYAML_EXT = True 14 del _yaml 15except ImportError: 16 HAS_LIBYAML_EXT = False 17 18 19_test_filenames = find_test_filenames(DATA) 20 21# ignore all datafiles 22collect_ignore_glob = ['data/*'] 23 24 25class PyYAMLItem(pytest.Item): 26 def __init__(self, parent=None, config=None, session=None, nodeid=None, function=None, filenames=None, **kwargs): 27 self._function = function 28 self._fargs = filenames or [] 29 30 super().__init__(os.path.basename(filenames[0]) if filenames else parent.name, parent, config, session, nodeid) 31 # this is gnarly since the type of fspath is private; fixed in pytest 7 to use pathlib on the `path` attr 32 if filenames: # pass the data file location as the test path 33 self.fspath = parent.fspath.__class__(filenames[0]) 34 self.lineno = 1 35 else: # pass the function location in the code 36 self.fspath = parent.fspath.__class__(function.__code__.co_filename) 37 self.lineno = function.__code__.co_firstlineno 38 39 def runtest(self): 40 self._function(verbose=True, *self._fargs) 41 42 def reportinfo(self): 43 return self.fspath, self.lineno, '' 44 45 46class PyYAMLCollector(pytest.Collector): 47 def __init__(self, name, parent=None, function=None, **kwargs): 48 self._function = function 49 self.fspath = parent.fspath.__class__(function.__code__.co_filename) 50 self.lineno = function.__code__.co_firstlineno 51 52 # avoid fspath deprecation warnings on pytest < 7 53 if hasattr(self, 'path') and 'fspath' in kwargs: 54 del kwargs['fspath'] 55 56 super().__init__(name=name, parent=parent, **kwargs) 57 58 def collect(self): 59 items = [] 60 61 unittest = getattr(self._function, 'unittest', None) 62 63 if unittest is True: # no filenames 64 items.append(PyYAMLItem.from_parent(parent=self, function=self._function, filenames=None)) 65 else: 66 for base, exts in _test_filenames: 67 filenames = [] 68 for ext in unittest: 69 if ext not in exts: 70 break 71 filenames.append(os.path.join(DATA, base + ext)) 72 else: 73 skip_exts = getattr(self._function, 'skip', []) 74 for skip_ext in skip_exts: 75 if skip_ext in exts: 76 break 77 else: 78 items.append(PyYAMLItem.from_parent(parent=self, function=self._function, filenames=filenames)) 79 80 return items or None 81 82 def reportinfo(self): 83 return self.fspath, self.lineno, '' 84 85 @classmethod 86 def from_parent(cls, parent, fspath, **kwargs): 87 return super().from_parent(parent=parent, fspath=fspath, **kwargs) 88 89 90@pytest.hookimpl(hookwrapper=True, trylast=True) 91def pytest_pycollect_makeitem(collector, name: str, obj: object): 92 outcome = yield 93 outcome.get_result() 94 if not callable(obj): 95 outcome.force_result(None) 96 return 97 unittest = getattr(obj, 'unittest', None) 98 99 if not unittest: 100 outcome.force_result(None) 101 return 102 103 if unittest is True: # no file list to run against, just return a test item instead of a collector 104 outcome.force_result(PyYAMLItem.from_parent(name=name, parent=collector, fspath=collector.fspath, function=obj)) 105 return 106 107 # there's a file list; return a collector to create individual items for each 108 outcome.force_result(PyYAMLCollector.from_parent(name=name, parent=collector, fspath=collector.fspath, function=obj)) 109 return 110 111 112def pytest_collection_modifyitems(session, config, items): 113 pass 114 115 116def pytest_ignore_collect(collection_path: pathlib.Path): 117 basename = collection_path.name 118 # ignore all Python files in this subtree for normal pytest collection 119 if basename not in ['test_yaml.py', 'test_yaml_ext.py']: 120 return True 121 122 # ignore extension tests (depending on config) 123 if basename == 'test_yaml_ext.py': 124 require_libyaml = os.environ.get('PYYAML_FORCE_LIBYAML', None) 125 if require_libyaml == '1' and not HAS_LIBYAML_EXT: 126 raise RuntimeError('PYYAML_FORCE_LIBYAML envvar is set, but libyaml extension is not available') 127 if require_libyaml == '0': 128 return True 129 if not HAS_LIBYAML_EXT: 130 warnings.warn('libyaml extension is not available, skipping libyaml tests') 131 return True 132 133