2Pytest infrastructure plugin for collecting virtual generated tests.
4The plugin adds a synthetic file named `__test_generated__.py` under the first
5collection directory from `config.args` and exposes a custom pytest hook,
6`pytest_generate_virtual_tests`.
8Other plugins can implement that hook to return generated `pytest.Item`
9objects, usually `pytest.Function` items.
11Generated tests are not backed by a real Python file, but they get a normal
12pytest parent chain and can use pytest fixtures when implemented as
13`pytest.Function` objects.
16from collections.abc
import Iterable
17from collections.abc
import Sequence
21from typing_extensions
import override
27except ModuleNotFoundError:
30_GENERATED_FILENAME =
'__test_generated__.py'
32_GENERATED_TESTS_DIR_KEY = pytest.StashKey[pathlib.Path |
None]()
33_COLLECTED_ITEMS_KEY = pytest.StashKey[list[pytest.Item]]()
38 Custom pytest (pluggy) hooks customizing generated tests.
40 @ingroup userver_testsuite
47 config: pytest.Config,
48 existing_items: Sequence[pytest.Item],
49 ) -> Iterable[pytest.Item]:
51 Returns generated pytest items to be collected under the virtual `__test_generated__.py` file.
53 raise NotImplementedError
56def pytest_addhooks(pluginmanager: pytest.PytestPluginManager) ->
None:
57 pluginmanager.add_hookspecs(GeneratedTestsPluginHooks)
60def pytest_configure(config: pytest.Config) ->
None:
65 config.stash[_GENERATED_TESTS_DIR_KEY] = _get_first_collection_directory(config)
66 config.stash[_COLLECTED_ITEMS_KEY] = []
69def pytest_itemcollected(item: pytest.Item) ->
None:
76 item.config.stash[_COLLECTED_ITEMS_KEY].append(item)
79def _get_first_collection_directory(config: pytest.Config) -> pathlib.Path |
None:
83 base = pathlib.Path(config.invocation_params.dir).resolve()
85 raw = config.args[0].split(
'::', 1)[0]
86 path = pathlib.Path(raw)
88 if not path.is_absolute():
102def pytest_collect_directory(path: pathlib.Path, parent: pytest.Collector):
109 generated_tests_dir = parent.config.stash[_GENERATED_TESTS_DIR_KEY]
111 if generated_tests_dir
is not None and path.resolve() == generated_tests_dir:
112 return _create_wrapped_directory(parent=parent, path=path)
117def _create_wrapped_directory(*, parent: pytest.Collector, path: pathlib.Path) -> pytest.Directory:
118 if path.joinpath(
'__init__.py').is_file():
119 return _GeneratedTestsPackage.from_parent(parent, path=path)
121 return _GeneratedTestsDir.from_parent(parent, path=path)
126 def collect(self) -> Iterable[pytest.Item | pytest.Collector]:
131 yield from super().collect()
133 yield _GeneratedTestsFile.from_parent(
135 path=self.path / _GENERATED_FILENAME,
143class _GeneratedTestsPackage(_GeneratedTestsDirectoryMixin, pytest.Package):
148 def __init__(self, *, path: pathlib.Path) ->
None:
149 self.__file__ = str(path)
154 def _getobj(self) -> object:
158 def collect(self) -> Iterable[pytest.Item | pytest.Collector]:
159 existing_items = self.config.stash[_COLLECTED_ITEMS_KEY]
161 hook_results = self.config.hook.pytest_generate_virtual_tests(
164 existing_items=existing_items,
167 for items
in hook_results: