userver: /data/code/service_template/third_party/userver/testsuite/pytest_plugins/pytest_userver/plugins/config.py Source File
Loading...
Searching...
No Matches
config.py
1"""
2Work with the configuration files of the service in testsuite.
3"""
4
5import logging
6import pathlib
7import types
8import typing
9
10import pytest
11import yaml
12
13
14# flake8: noqa E266
15
29USERVER_CONFIG_HOOKS = [
30 'userver_config_http_server',
31 'userver_config_http_client',
32 'userver_config_logging',
33 'userver_config_testsuite',
34 'userver_config_testsuite_support',
35 'userver_config_secdist',
36]
37
38
39# @cond
40
41
42logger = logging.getLogger(__name__)
43
44
45class _UserverConfigPlugin:
46 def __init__(self):
47 self._config_hooks = []
48
49 @property
50 def userver_config_hooks(self):
51 return self._config_hooks
52
53 def pytest_plugin_registered(self, plugin, manager):
54 if not isinstance(plugin, types.ModuleType):
55 return
56 uhooks = getattr(plugin, 'USERVER_CONFIG_HOOKS', None)
57 if uhooks is not None:
58 self._config_hooks.extend(uhooks)
59
60
61class _UserverConfig(typing.NamedTuple):
62 config_yaml: dict
63 config_vars: dict
64
65
66def pytest_configure(config):
67 config.pluginmanager.register(_UserverConfigPlugin(), 'userver_config')
68 config.addinivalue_line(
69 'markers', 'config: per-test dynamic config values',
70 )
71
72
73def pytest_addoption(parser) -> None:
74 group = parser.getgroup('userver-config')
75 group.addoption(
76 '--service-log-level',
77 type=str.lower,
78 default='debug',
79 choices=['trace', 'debug', 'info', 'warning', 'error', 'critical'],
80 )
81 group.addoption(
82 '--service-config',
83 type=pathlib.Path,
84 help='Path to service.yaml file.',
85 )
86 group.addoption(
87 '--service-config-vars',
88 type=pathlib.Path,
89 help='Path to config_vars.yaml file.',
90 )
91 group.addoption(
92 '--service-secdist',
93 type=pathlib.Path,
94 help='Path to secure_data.json file.',
95 )
96 group.addoption(
97 '--config-fallback',
98 type=pathlib.Path,
99 help='Path to dynamic config fallback file.',
100 )
101
102
103# @endcond
104
105
106@pytest.fixture(scope='session')
107def service_config_path(pytestconfig) -> pathlib.Path:
108 """
109 Returns the path to service.yaml file set by command line
110 `--service-config` option.
111
112 Override this fixture to change the way path to service.yaml is provided.
113
114 @ingroup userver_testsuite_fixtures
115 """
116 return pytestconfig.option.service_config
117
118
119@pytest.fixture(scope='session')
120def service_config_vars_path(pytestconfig) -> typing.Optional[pathlib.Path]:
121 """
122 Returns the path to config_vars.yaml file set by command line
123 `--service-config-vars` option.
124
125 Override this fixture to change the way path to config_vars.yaml is
126 provided.
127
128 @ingroup userver_testsuite_fixtures
129 """
130 return pytestconfig.option.service_config_vars
131
132
133@pytest.fixture(scope='session')
134def service_secdist_path(pytestconfig) -> typing.Optional[pathlib.Path]:
135 """
136 Returns the path to secure_data.json file set by command line
137 `--service-secdist` option.
138
139 Override this fixture to change the way path to secure_data.json is
140 provided.
141
142 @ingroup userver_testsuite_fixtures
143 """
144 return pytestconfig.option.service_secdist
145
146
147@pytest.fixture(scope='session')
148def config_fallback_path(pytestconfig) -> pathlib.Path:
149 """
150 Returns the path to dynamic config fallback file set by command line
151 `--config-fallback` option.
152
153 Override this fixture to change the way path to dynamic config fallback is
154 provided.
155
156 @ingroup userver_testsuite_fixtures
157 """
158 return pytestconfig.option.config_fallback
159
160
161@pytest.fixture(scope='session')
162def service_tmpdir(service_binary, tmp_path_factory):
163 """
164 Returns the path for temporary files. The path is the same for the whole
165 session and files are not removed (at least by this fixture) between
166 tests.
167
168 @ingroup userver_testsuite_fixtures
169 """
170 return tmp_path_factory.mktemp(pathlib.Path(service_binary).name)
171
172
173@pytest.fixture(scope='session')
175 service_tmpdir, service_config_yaml,
176) -> pathlib.Path:
177 """
178 Dumps the contents of the service_config_yaml into a static config for
179 testsuite and returns the path to the config file.
180
181 @ingroup userver_testsuite_fixtures
182 """
183 dst_path = service_tmpdir / 'config.yaml'
184
185 config_text = yaml.dump(service_config_yaml)
186 logger.debug(
187 'userver fixture "service_config_path_temp" writes the patched static '
188 'config to "%s":\n%s',
189 dst_path,
190 config_text,
191 )
192 dst_path.write_text(config_text)
193
194 return dst_path
195
196
197@pytest.fixture(scope='session')
198def service_config_yaml(_service_config) -> dict:
199 """
200 Returns the static config values after the USERVER_CONFIG_HOOKS were
201 applied (if any).
202
203 @ingroup userver_testsuite_fixtures
204 """
205 return _service_config.config_yaml
206
207
208@pytest.fixture(scope='session')
209def service_config_vars(_service_config) -> dict:
210 """
211 Returns the static config variables (config_vars.yaml) values after the
212 USERVER_CONFIG_HOOKS were applied (if any).
213
214 @ingroup userver_testsuite_fixtures
215 """
216 return _service_config.config_vars
217
218
219@pytest.fixture(scope='session')
220def _service_config(
221 pytestconfig,
222 request,
223 service_tmpdir,
224 service_config_path,
225 service_config_vars_path,
226) -> _UserverConfig:
227 config_vars: dict
228 config_yaml: dict
229
230 with open(service_config_path, mode='rt') as fp:
231 config_yaml = yaml.safe_load(fp)
232
233 if service_config_vars_path:
234 with open(service_config_vars_path, mode='rt') as fp:
235 config_vars = yaml.safe_load(fp)
236 else:
237 config_vars = {}
238
239 plugin = pytestconfig.pluginmanager.get_plugin('userver_config')
240 for hook in plugin.userver_config_hooks:
241 if not callable(hook):
242 hook_func = request.getfixturevalue(hook)
243 else:
244 hook_func = hook
245 hook_func(config_yaml, config_vars)
246
247 if not config_vars:
248 config_yaml.pop('config_vars', None)
249 else:
250 config_vars_path = service_tmpdir / 'config_vars.yaml'
251 config_vars_text = yaml.dump(config_vars)
252 logger.debug(
253 'userver fixture "service_config" writes the patched static '
254 'config vars to "%s":\n%s',
255 config_vars_path,
256 config_vars_text,
257 )
258 config_vars_path.write_text(config_vars_text)
259 config_yaml['config_vars'] = str(config_vars_path)
260
261 return _UserverConfig(config_yaml=config_yaml, config_vars=config_vars)
262
263
264@pytest.fixture(scope='session')
265def userver_config_http_server(service_port, monitor_port):
266 """
267 Returns a function that adjusts the static configuration file for testsuite.
268 Sets the `server.listener.port` to listen on
269 @ref pytest_userver.plugins.base.service_port "service_port" fixture value;
270 sets the `server.listener-monitor.port` to listen on
271 @ref pytest_userver.plugins.base.monitor_port "monitor_port"
272 fixture value.
273
274 @ingroup userver_testsuite_fixtures
275 """
276
277 def _patch_config(config_yaml, config_vars):
278 components = config_yaml['components_manager']['components']
279 if 'server' in components:
280 server = components['server']
281 if 'listener' in server:
282 server['listener']['port'] = service_port
283
284 if 'listener-monitor' in server:
285 server['listener-monitor']['port'] = monitor_port
286
287 return _patch_config
288
289
290@pytest.fixture(scope='session')
291def allowed_url_prefixes_extra() -> typing.List[str]:
292 """
293 By default, userver HTTP client is only allowed to talk to mockserver
294 when running in testsuite. This makes tests repeatable and encapsulated.
295
296 Override this fixture to whitelist some additional URLs.
297 It is still strongly advised to only talk to localhost in tests.
298
299 @ingroup userver_testsuite_fixtures
300 """
301 return []
302
303
304@pytest.fixture(scope='session')
306 mockserver_info, mockserver_ssl_info, allowed_url_prefixes_extra,
307):
308 """
309 Returns a function that adjusts the static configuration file for testsuite.
310 Sets increased timeout and limits allowed URLs for `http-client` component.
311
312 @ingroup userver_testsuite_fixtures
313 """
314
315 def patch_config(config, config_vars):
316 components: dict = config['components_manager']['components']
317 if not {'http-client', 'testsuite-support'}.issubset(
318 components.keys(),
319 ):
320 return
321 http_client = components['http-client'] or {}
322 http_client['testsuite-enabled'] = True
323 http_client['testsuite-timeout'] = '10s'
324
325 allowed_urls = [mockserver_info.base_url]
326 if mockserver_ssl_info:
327 allowed_urls.append(mockserver_ssl_info.base_url)
328 allowed_urls += allowed_url_prefixes_extra
329 http_client['testsuite-allowed-url-prefixes'] = allowed_urls
330
331 return patch_config
332
333
334@pytest.fixture(scope='session')
335def userver_config_logging(pytestconfig):
336 """
337 Returns a function that adjusts the static configuration file for testsuite.
338 Sets the `logging.loggers.default` to log to `@stderr` with level set
339 from `--service-log-level` pytest configuration option.
340
341 @ingroup userver_testsuite_fixtures
342 """
343 log_level = pytestconfig.option.service_log_level
344
345 def _patch_config(config_yaml, config_vars):
346 components = config_yaml['components_manager']['components']
347 if 'logging' in components:
348 components['logging']['loggers'] = {
349 'default': {
350 'file_path': '@stderr',
351 'level': log_level,
352 'overflow_behavior': 'discard',
353 },
354 }
355 config_vars['logger_level'] = log_level
356
357 return _patch_config
358
359
360@pytest.fixture(scope='session')
361def userver_config_testsuite(mockserver_info):
362 """
363 Returns a function that adjusts the static configuration file for testsuite.
364 Sets the `testsuite-enabled` in config_vars.yaml to `True`; sets the
365 `tests-control.testpoint-url` to mockserver URL.
366
367 @ingroup userver_testsuite_fixtures
368 """
369
370 def _patch_config(config_yaml, config_vars):
371 config_vars['testsuite-enabled'] = True
372 components = config_yaml['components_manager']['components']
373 if 'tests-control' in components:
374 components['tests-control']['testpoint-url'] = mockserver_info.url(
375 'testpoint',
376 )
377
378 return _patch_config
379
380
381@pytest.fixture(scope='session')
383 """
384 Returns a function that adjusts the static configuration file for testsuite.
385 Sets up `testsuite-support` component, which:
386
387 - increases timeouts for userver drivers
388 - disables periodic cache updates
389 - enables testsuite tasks
390
391 @ingroup userver_testsuite_fixtures
392 """
393
394 def _set_postgresql_options(testsuite_support: dict) -> None:
395 testsuite_support['testsuite-pg-execute-timeout'] = '35s'
396 testsuite_support['testsuite-pg-statement-timeout'] = '30s'
397 testsuite_support['testsuite-pg-readonly-master-expected'] = True
398
399 def _set_redis_timeout(testsuite_support: dict) -> None:
400 testsuite_support['testsuite-redis-timeout-connect'] = '40s'
401 testsuite_support['testsuite-redis-timeout-single'] = '30s'
402 testsuite_support['testsuite-redis-timeout-all'] = '30s'
403
404 def _disable_cache_periodic_update(testsuite_support: dict) -> None:
405 testsuite_support['testsuite-periodic-update-enabled'] = False
406
407 def patch_config(config, config_vars) -> None:
408 components: dict = config['components_manager']['components']
409 if 'testsuite-support' not in components:
410 return
411 testsuite_support = components['testsuite-support'] or {}
412 _set_postgresql_options(testsuite_support)
413 _set_redis_timeout(testsuite_support)
414 service_runner = pytestconfig.getoption('--service-runner-mode', False)
415 if not service_runner:
416 _disable_cache_periodic_update(testsuite_support)
417 testsuite_support['testsuite-tasks-enabled'] = not service_runner
418 components['testsuite-support'] = testsuite_support
419
420 return patch_config
421
422
423@pytest.fixture(scope='session')
424def userver_config_secdist(service_secdist_path):
425 """
426 Returns a function that adjusts the static configuration file for testsuite.
427 Sets the `default-secdist-provider.config` to the value of
428 @ref pytest_userver.plugins.config.service_secdist_path "service_secdist_path"
429 fixture.
430
431 @ingroup userver_testsuite_fixtures
432 """
433
434 def _patch_config(config_yaml, config_vars):
435 if not service_secdist_path:
436 return
437
438 components = config_yaml['components_manager']['components']
439 if 'default-secdist-provider' not in components:
440 return
441
442 if not service_secdist_path.is_file():
443 raise ValueError(
444 f'"{service_secdist_path}" is not a file. Provide a '
445 f'"--service-secdist" pytest option or override the '
446 f'"service_secdist_path" fixture.',
447 )
448 components['default-secdist-provider']['config'] = str(
449 service_secdist_path,
450 )
451
452 return _patch_config