userver
C++ Async Framework
Toggle main menu visibility
Loading...
Searching...
No Matches
generated_tests.py
1
"""
2
Pytest infrastructure plugin for collecting virtual generated tests.
3
4
The plugin adds a synthetic file named `__test_generated__.py` under the first
5
collection directory from `config.args` and exposes a custom pytest hook,
6
`pytest_generate_virtual_tests`.
7
8
Other plugins can implement that hook to return generated `pytest.Item`
9
objects, usually `pytest.Function` items.
10
11
Generated tests are not backed by a real Python file, but they get a normal
12
pytest parent chain and can use pytest fixtures when implemented as
13
`pytest.Function` objects.
14
"""
15
16
from
collections.abc
import
Iterable
17
from
collections.abc
import
Sequence
18
import
pathlib
19
20
import
pytest
21
from
typing_extensions
import
override
22
23
try
:
24
import
yatest.common
# noqa: F401
25
26
_IS_ARCADIA =
True
27
except
ModuleNotFoundError:
28
_IS_ARCADIA =
False
29
30
_GENERATED_FILENAME =
'__test_generated__.py'
31
32
_GENERATED_TESTS_DIR_KEY = pytest.StashKey[pathlib.Path |
None
]()
33
_COLLECTED_ITEMS_KEY = pytest.StashKey[list[pytest.Item]]()
34
35
36
class
GeneratedTestsPluginHooks
:
37
"""
38
Custom pytest (pluggy) hooks customizing generated tests.
39
40
@ingroup userver_testsuite
41
"""
42
43
@pytest.hookspec
44
def
pytest_generate_virtual_tests
(
45
self,
46
parent: pytest.File,
47
config: pytest.Config,
48
existing_items: Sequence[pytest.Item],
49
) -> Iterable[pytest.Item]:
50
"""
51
Returns generated pytest items to be collected under the virtual `__test_generated__.py` file.
52
"""
53
raise
NotImplementedError
54
55
56
def
pytest_addhooks(pluginmanager: pytest.PytestPluginManager) ->
None
:
57
pluginmanager.add_hookspecs(GeneratedTestsPluginHooks)
58
59
60
def
pytest_configure(config: pytest.Config) ->
None
:
61
if
_IS_ARCADIA:
62
# Skip trying to touch disk instead of resfs.
63
return
64
65
config.stash[_GENERATED_TESTS_DIR_KEY] = _get_first_collection_directory(config)
66
config.stash[_COLLECTED_ITEMS_KEY] = []
67
68
69
def
pytest_itemcollected(item: pytest.Item) ->
None
:
70
if
_IS_ARCADIA:
71
return
72
73
# Accumulate the real items as pytest collects them. Since the wrapped
74
# directory yields the virtual `__test_generated__.py` file last, every real
75
# item has already fired this hook by the time the virtual file is collected.
76
item.config.stash[_COLLECTED_ITEMS_KEY].append(item)
77
78
79
def
_get_first_collection_directory(config: pytest.Config) -> pathlib.Path |
None
:
80
if
not
config.args:
81
return
None
82
83
base = pathlib.Path(config.invocation_params.dir).resolve()
84
85
raw = config.args[0].split(
'::'
, 1)[0]
86
path = pathlib.Path(raw)
87
88
if
not
path.is_absolute():
89
path = base / path
90
91
path = path.resolve()
92
93
if
path.is_file():
94
return
path.parent
95
96
if
path.is_dir():
97
return
path
98
99
return
None
100
101
102
def
pytest_collect_directory(path: pathlib.Path, parent: pytest.Collector):
103
if
_IS_ARCADIA:
104
# The OSS way of injecting generated tests does not work there, because:
105
# * pytest_collect_directory hook currently does not work (could be worked around)
106
# * only some of the targets need injected tests (could be worked around too)
107
return
None
108
109
generated_tests_dir = parent.config.stash[_GENERATED_TESTS_DIR_KEY]
110
111
if
generated_tests_dir
is
not
None
and
path.resolve() == generated_tests_dir:
112
return
_create_wrapped_directory(parent=parent, path=path)
113
114
return
None
115
116
117
def
_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)
120
121
return
_GeneratedTestsDir.from_parent(parent, path=path)
122
123
124
class
_GeneratedTestsDirectoryMixin
(pytest.Directory):
125
@override
126
def
collect(self) -> Iterable[pytest.Item | pytest.Collector]:
127
# Yield the real children first and let pytest collect them normally; the
128
# session fires `pytest_itemcollected` for each resulting item, and our
129
# hook accumulates them into the config stash. The virtual file is yielded
130
# last, so by the time it is collected all real items are already known.
131
yield
from
super().collect()
132
133
yield
_GeneratedTestsFile.from_parent(
134
self,
135
path=self.path / _GENERATED_FILENAME,
136
)
137
138
139
class
_GeneratedTestsDir
(
_GeneratedTestsDirectoryMixin
, pytest.Dir):
140
pass
141
142
143
class
_GeneratedTestsPackage(_GeneratedTestsDirectoryMixin, pytest.Package):
144
pass
145
146
147
class
_FakeModule
:
148
def
__init__(self, *, path: pathlib.Path) ->
None
:
149
self.__file__ = str(path)
150
151
152
class
_GeneratedTestsFile
(pytest.Module):
153
@override
154
def
_getobj(self) -> object:
155
return
_FakeModule
(path=self.path)
156
157
@override
158
def
collect(self) -> Iterable[pytest.Item | pytest.Collector]:
159
existing_items = self.config.stash[_COLLECTED_ITEMS_KEY]
160
161
hook_results = self.config.hook.pytest_generate_virtual_tests(
162
parent=self,
163
config=self.config,
164
existing_items=existing_items,
165
)
166
167
for
items
in
hook_results:
168
yield
from
items
testsuite
pytest_plugins
pytest_userver
plugins
generated_tests.py
Generated on
for userver by
Doxygen
1.17.0