2Work with the configuration files of the service in testsuite.
32USERVER_CONFIG_HOOKS = [
33 'userver_config_http_server',
34 'userver_config_http_client',
35 'userver_config_logging',
36 'userver_config_testsuite',
37 'userver_config_secdist',
38 'userver_config_testsuite_middleware',
45logger = logging.getLogger(__name__)
48class _UserverConfigPlugin:
50 self._config_hooks = []
53 def userver_config_hooks(self):
54 return self._config_hooks
56 def pytest_plugin_registered(self, plugin, manager):
57 if not isinstance(plugin, types.ModuleType):
59 uhooks = getattr(plugin,
'USERVER_CONFIG_HOOKS',
None)
60 if uhooks
is not None:
61 self._config_hooks.extend(uhooks)
64class _UserverConfig(typing.NamedTuple):
69def pytest_configure(config):
70 config.pluginmanager.register(_UserverConfigPlugin(),
'userver_config')
71 config.addinivalue_line(
72 'markers',
'config: per-test dynamic config values',
76def pytest_addoption(parser) -> None:
77 group = parser.getgroup(
'userver-config')
79 '--service-log-level',
81 choices=[
'trace',
'debug',
'info',
'warning',
'error',
'critical'],
86 help=
'Path to service.yaml file.',
89 '--service-config-vars',
91 help=
'Path to config_vars.yaml file.',
96 help=
'Path to secure_data.json file.',
101 help=
'Path to dynamic config fallback file.',
108@pytest.fixture(scope='session')
111 Returns the path to service.yaml file set by command line
112 `--service-config` option.
114 Override this fixture to change the way path to service.yaml is provided.
116 @ingroup userver_testsuite_fixtures
118 return pytestconfig.option.service_config
121@pytest.fixture(scope='session')
124 Returns the path to config_vars.yaml file set by command line
125 `--service-config-vars` option.
127 Override this fixture to change the way path to config_vars.yaml is
130 @ingroup userver_testsuite_fixtures
132 return pytestconfig.option.service_config_vars
135@pytest.fixture(scope='session')
138 Returns the path to secure_data.json file set by command line
139 `--service-secdist` option.
141 Override this fixture to change the way path to secure_data.json is
144 @ingroup userver_testsuite_fixtures
146 return pytestconfig.option.service_secdist
149@pytest.fixture(scope='session')
152 Returns the path to dynamic config fallback file set by command line
153 `--config-fallback` option.
155 Override this fixture to change the way path to dynamic config fallback is
158 @ingroup userver_testsuite_fixtures
160 return pytestconfig.option.config_fallback
163@pytest.fixture(scope='session')
166 Returns the path for temporary files. The path is the same for the whole
167 session and files are not removed (at least by this fixture) between
170 @ingroup userver_testsuite_fixtures
172 return tmp_path_factory.mktemp(pathlib.Path(service_binary).name)
175@pytest.fixture(scope='session')
177 service_tmpdir, service_config, service_config_yaml,
180 Dumps the contents of the service_config_yaml into a static config for
181 testsuite and returns the path to the config file.
183 @ingroup userver_testsuite_fixtures
185 dst_path = service_tmpdir /
'config.yaml'
188 'userver fixture "service_config_path_temp" writes the patched static '
189 'config to "%s" equivalent to:\n%s',
191 yaml.dump(service_config),
193 dst_path.write_text(yaml.dump(service_config_yaml))
198@pytest.fixture(scope='session')
201 Returns the static config values after the USERVER_CONFIG_HOOKS were
202 applied (if any). Prefer using
203 pytest_userver.plugins.config.service_config
205 @ingroup userver_testsuite_fixtures
207 return _service_config_hooked.config_yaml
210@pytest.fixture(scope='session')
213 Returns the static config variables (config_vars.yaml) values after the
214 USERVER_CONFIG_HOOKS were applied (if any). Prefer using
215 pytest_userver.plugins.config.service_config
217 @ingroup userver_testsuite_fixtures
219 return _service_config_hooked.config_vars
222def _substitute_values(config, service_config_vars: dict, service_env) ->
None:
223 if isinstance(config, dict):
224 for key, value
in config.items():
225 if not isinstance(value, str):
226 _substitute_values(value, service_config_vars, service_env)
229 if not value.startswith(
'$'):
232 new_value = service_config_vars.get(value[1:])
233 if new_value
is not None:
234 config[key] = new_value
237 env = config.get(f
'{key}#env')
240 new_value = service_env.get(service_env)
242 new_value = os.environ.get(env)
244 config[key] = new_value
247 fallback = config.get(f
'{key}#fallback')
249 config[key] = fallback
251 if isinstance(config, list):
252 for i, value
in enumerate(config):
253 if not isinstance(value, str):
254 _substitute_values(value, service_config_vars, service_env)
257 if not value.startswith(
'$'):
260 new_value = service_config_vars.get(value[1:])
261 if new_value
is not None:
262 config[i] = new_value
265@pytest.fixture(scope='session')
267 service_config_yaml, service_config_vars, service_env,
270 Returns the static config values after the USERVER_CONFIG_HOOKS were
271 applied (if any) and with all the '$', environment and fallback variables
274 @ingroup userver_testsuite_fixtures
276 config = copy.deepcopy(service_config_yaml)
277 _substitute_values(config, service_config_vars, service_env)
278 config.pop(
'config_vars',
None)
282@pytest.fixture(scope='session')
283def _original_service_config(
284 service_config_path, service_config_vars_path,
289 with open(service_config_path, mode=
'rt')
as fp:
290 config_yaml = yaml.safe_load(fp)
292 if service_config_vars_path:
293 with open(service_config_vars_path, mode=
'rt')
as fp:
294 config_vars = yaml.safe_load(fp)
298 return _UserverConfig(config_yaml=config_yaml, config_vars=config_vars)
301@pytest.fixture(scope='session')
302def _service_config_hooked(
303 pytestconfig, request, service_tmpdir, _original_service_config,
305 config_yaml = copy.deepcopy(_original_service_config.config_yaml)
306 config_vars = copy.deepcopy(_original_service_config.config_vars)
308 plugin = pytestconfig.pluginmanager.get_plugin(
'userver_config')
309 for hook
in plugin.userver_config_hooks:
310 if not callable(hook):
311 hook_func = request.getfixturevalue(hook)
314 hook_func(config_yaml, config_vars)
317 config_yaml.pop(
'config_vars',
None)
319 config_vars_path = service_tmpdir /
'config_vars.yaml'
320 config_vars_text = yaml.dump(config_vars)
322 'userver fixture "service_config" writes the patched static '
323 'config vars to "%s":\n%s',
327 config_vars_path.write_text(config_vars_text)
328 config_yaml[
'config_vars'] = str(config_vars_path)
330 return _UserverConfig(config_yaml=config_yaml, config_vars=config_vars)
333@pytest.fixture(scope='session')
336 Returns a function that adjusts the static configuration file for testsuite.
337 Sets the `server.listener.port` to listen on
338 @ref pytest_userver.plugins.base.service_port "service_port" fixture value;
339 sets the `server.listener-monitor.port` to listen on
340 @ref pytest_userver.plugins.base.monitor_port "monitor_port"
343 @ingroup userver_testsuite_fixtures
346 def _patch_config(config_yaml, config_vars):
347 components = config_yaml[
'components_manager'][
'components']
348 if 'server' in components:
349 server = components[
'server']
350 if 'listener' in server:
351 server[
'listener'][
'port'] = service_port
353 if 'listener-monitor' in server:
354 server[
'listener-monitor'][
'port'] = monitor_port
359@pytest.fixture(scope='session')
362 By default, userver HTTP client is only allowed to talk to mockserver
363 when running in testsuite. This makes tests repeatable and encapsulated.
365 Override this fixture to whitelist some additional URLs.
366 It is still strongly advised to only talk to localhost in tests.
368 @ingroup userver_testsuite_fixtures
373@pytest.fixture(scope='session')
375 mockserver_info, mockserver_ssl_info, allowed_url_prefixes_extra,
378 Returns a function that adjusts the static configuration file for testsuite.
379 Sets increased timeout and limits allowed URLs for `http-client` component.
381 @ingroup userver_testsuite_fixtures
384 def patch_config(config, config_vars):
385 components: dict = config[
'components_manager'][
'components']
386 if not {
'http-client',
'testsuite-support'}.issubset(
390 http_client = components[
'http-client']
or {}
391 http_client[
'testsuite-enabled'] =
True
392 http_client[
'testsuite-timeout'] =
'10s'
394 allowed_urls = [mockserver_info.base_url]
395 if mockserver_ssl_info:
396 allowed_urls.append(mockserver_ssl_info.base_url)
397 allowed_urls += allowed_url_prefixes_extra
398 http_client[
'testsuite-allowed-url-prefixes'] = allowed_urls
403@pytest.fixture(scope='session')
406 Default log level to use in userver if no caoomand line option was provided.
410 @ingroup userver_testsuite_fixtures
415@pytest.fixture(scope='session')
418 Returns --service-log-level value if provided, otherwise returns
419 userver_default_log_level() value from fixture.
421 @ingroup userver_testsuite_fixtures
423 if pytestconfig.option.service_log_level:
424 return pytestconfig.option.service_log_level
425 return userver_default_log_level
428@pytest.fixture(scope='session')
431 Returns a function that adjusts the static configuration file for testsuite.
432 Sets the `logging.loggers.default` to log to `@stderr` with level set
433 from `--service-log-level` pytest configuration option.
435 @ingroup userver_testsuite_fixtures
438 if _service_logfile_path:
439 default_file_path = str(_service_logfile_path)
441 default_file_path =
'@stderr'
443 def _patch_config(config_yaml, config_vars):
444 components = config_yaml[
'components_manager'][
'components']
445 if 'logging' in components:
446 loggers = components[
'logging'].setdefault(
'loggers', {})
447 for logger
in loggers.values():
448 logger[
'file_path'] =
'@null'
449 loggers[
'default'] = {
450 'file_path': default_file_path,
451 'level': userver_log_level,
452 'overflow_behavior':
'discard',
454 config_vars[
'logger_level'] = userver_log_level
459@pytest.fixture(scope='session')
462 Returns a function that adjusts the static configuration file for testsuite.
464 Sets up `testsuite-support` component, which:
466 - increases timeouts for userver drivers
467 - disables periodic cache updates
468 - enables testsuite tasks
470 Sets the `testsuite-enabled` in config_vars.yaml to `True`; sets the
471 `tests-control.testpoint-url` to mockserver URL.
473 @ingroup userver_testsuite_fixtures
476 def _set_postgresql_options(testsuite_support: dict) ->
None:
477 testsuite_support[
'testsuite-pg-execute-timeout'] =
'35s'
478 testsuite_support[
'testsuite-pg-statement-timeout'] =
'30s'
479 testsuite_support[
'testsuite-pg-readonly-master-expected'] =
True
481 def _set_redis_timeout(testsuite_support: dict) ->
None:
482 testsuite_support[
'testsuite-redis-timeout-connect'] =
'40s'
483 testsuite_support[
'testsuite-redis-timeout-single'] =
'30s'
484 testsuite_support[
'testsuite-redis-timeout-all'] =
'30s'
486 def _disable_cache_periodic_update(testsuite_support: dict) ->
None:
487 testsuite_support[
'testsuite-periodic-update-enabled'] =
False
489 def patch_config(config, config_vars) -> None:
490 components: dict = config[
'components_manager'][
'components']
491 if 'testsuite-support' not in components:
493 testsuite_support = components[
'testsuite-support']
or {}
494 testsuite_support[
'testsuite-increased-timeout'] =
'30s'
495 _set_postgresql_options(testsuite_support)
496 _set_redis_timeout(testsuite_support)
497 service_runner = pytestconfig.getoption(
'--service-runner-mode',
False)
498 if not service_runner:
499 _disable_cache_periodic_update(testsuite_support)
500 testsuite_support[
'testsuite-tasks-enabled'] =
not service_runner
502 'testsuite-periodic-dumps-enabled'
503 ] =
'$userver-dumps-periodic'
504 components[
'testsuite-support'] = testsuite_support
506 config_vars[
'testsuite-enabled'] =
True
507 if 'tests-control' in components:
508 components[
'tests-control'][
'testpoint-url'] = mockserver_info.url(
515@pytest.fixture(scope='session')
518 Returns a function that adjusts the static configuration file for testsuite.
519 Sets the `default-secdist-provider.config` to the value of
520 @ref pytest_userver.plugins.config.service_secdist_path "service_secdist_path"
523 @ingroup userver_testsuite_fixtures
526 def _patch_config(config_yaml, config_vars):
527 if not service_secdist_path:
530 components = config_yaml[
'components_manager'][
'components']
531 if 'default-secdist-provider' not in components:
534 if not service_secdist_path.is_file():
536 f
'"{service_secdist_path}" is not a file. Provide a '
537 f
'"--service-secdist" pytest option or override the '
538 f
'"service_secdist_path" fixture.',
540 components[
'default-secdist-provider'][
'config'] = str(
541 service_secdist_path,
547@pytest.fixture(scope='session')
548def userver_config_testsuite_middleware(
549 userver_testsuite_middleware_enabled: bool,
551 def patch_config(config_yaml, config_vars):
552 if not userver_testsuite_middleware_enabled:
555 components = config_yaml[
'components_manager'][
'components']
556 if 'server' not in components:
559 pipeline_builder = components.setdefault(
560 'default-server-middleware-pipeline-builder', {},
562 middlewares = pipeline_builder.setdefault(
'append', [])
563 middlewares.append(
'testsuite-exceptions-handling-middleware')
568@pytest.fixture(scope='session')
570 """Enabled testsuite middleware."""