2Work with the configuration files of the service in testsuite.
31USERVER_CONFIG_HOOKS = [
32 'userver_config_http_server',
33 'userver_config_http_client',
34 'userver_config_logging',
35 'userver_config_logging_otlp',
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')
73def pytest_addoption(parser) -> None:
74 group = parser.getgroup(
'userver-config')
76 '--service-log-level',
78 choices=[
'trace',
'debug',
'info',
'warning',
'error',
'critical'],
83 help=
'Path to service.yaml file.',
86 '--service-config-vars',
88 help=
'Path to config_vars.yaml file.',
93 help=
'Path to secure_data.json file.',
98 help=
'Path to dynamic config fallback file.',
105@pytest.fixture(scope='session')
108 Returns the path to service.yaml file set by command line
109 `--service-config` option.
111 Override this fixture to change the way path to service.yaml is provided.
113 @ingroup userver_testsuite_fixtures
115 return pytestconfig.option.service_config
118@pytest.fixture(scope='session')
121 Returns the path to config_vars.yaml file set by command line
122 `--service-config-vars` option.
124 Override this fixture to change the way path to config_vars.yaml is
127 @ingroup userver_testsuite_fixtures
129 return pytestconfig.option.service_config_vars
132@pytest.fixture(scope='session')
135 Returns the path to secure_data.json file set by command line
136 `--service-secdist` option.
138 Override this fixture to change the way path to secure_data.json is
141 @ingroup userver_testsuite_fixtures
143 return pytestconfig.option.service_secdist
146@pytest.fixture(scope='session')
149 Returns the path to dynamic config fallback file set by command line
150 `--config-fallback` option.
152 Override this fixture to change the way path to dynamic config fallback is
155 @ingroup userver_testsuite_fixtures
157 return pytestconfig.option.config_fallback
160@pytest.fixture(scope='session')
163 Returns the path for temporary files. The path is the same for the whole
164 session and files are not removed (at least by this fixture) between
167 @ingroup userver_testsuite_fixtures
169 return tmp_path_factory.mktemp(
170 pathlib.Path(service_binary).name, numbered=
False,
174@pytest.fixture(scope='session')
176 service_tmpdir, service_config, service_config_yaml,
179 Dumps the contents of the service_config_yaml into a static config for
180 testsuite and returns the path to the config file.
182 @ingroup userver_testsuite_fixtures
184 dst_path = service_tmpdir /
'config.yaml'
187 'userver fixture "service_config_path_temp" writes the patched static '
188 'config to "%s" equivalent to:\n%s',
190 yaml.dump(service_config),
192 dst_path.write_text(yaml.dump(service_config_yaml))
197@pytest.fixture(scope='session')
200 Returns the static config values after the USERVER_CONFIG_HOOKS were
201 applied (if any). Prefer using
202 pytest_userver.plugins.config.service_config
204 @ingroup userver_testsuite_fixtures
206 return _service_config_hooked.config_yaml
209@pytest.fixture(scope='session')
212 Returns the static config variables (config_vars.yaml) values after the
213 USERVER_CONFIG_HOOKS were applied (if any). Prefer using
214 pytest_userver.plugins.config.service_config
216 @ingroup userver_testsuite_fixtures
218 return _service_config_hooked.config_vars
221def _substitute_values(config, service_config_vars: dict, service_env) ->
None:
222 if isinstance(config, dict):
223 for key, value
in config.items():
224 if not isinstance(value, str):
225 _substitute_values(value, service_config_vars, service_env)
228 if not value.startswith(
'$'):
231 new_value = service_config_vars.get(value[1:])
232 if new_value
is not None:
233 config[key] = new_value
236 env = config.get(f
'{key}#env')
239 new_value = service_env.get(service_env)
241 new_value = os.environ.get(env)
243 config[key] = new_value
246 fallback = config.get(f
'{key}#fallback')
248 config[key] = fallback
250 if isinstance(config, list):
251 for i, value
in enumerate(config):
252 if not isinstance(value, str):
253 _substitute_values(value, service_config_vars, service_env)
256 if not value.startswith(
'$'):
259 new_value = service_config_vars.get(value[1:])
260 if new_value
is not None:
261 config[i] = new_value
264@pytest.fixture(scope='session')
266 service_config_yaml, service_config_vars, service_env,
269 Returns the static config values after the USERVER_CONFIG_HOOKS were
270 applied (if any) and with all the '$', environment and fallback variables
273 @ingroup userver_testsuite_fixtures
275 config = copy.deepcopy(service_config_yaml)
276 _substitute_values(config, service_config_vars, service_env)
277 config.pop(
'config_vars',
None)
281@pytest.fixture(scope='session')
282def _original_service_config(
283 service_config_path, service_config_vars_path,
288 with open(service_config_path, mode=
'rt')
as fp:
289 config_yaml = yaml.safe_load(fp)
291 if service_config_vars_path:
292 with open(service_config_vars_path, mode=
'rt')
as fp:
293 config_vars = yaml.safe_load(fp)
297 return _UserverConfig(config_yaml=config_yaml, config_vars=config_vars)
300@pytest.fixture(scope='session')
301def _service_config_hooked(
302 pytestconfig, request, service_tmpdir, _original_service_config,
304 config_yaml = copy.deepcopy(_original_service_config.config_yaml)
305 config_vars = copy.deepcopy(_original_service_config.config_vars)
307 plugin = pytestconfig.pluginmanager.get_plugin(
'userver_config')
308 for hook
in plugin.userver_config_hooks:
309 if not callable(hook):
310 hook_func = request.getfixturevalue(hook)
313 hook_func(config_yaml, config_vars)
316 config_yaml.pop(
'config_vars',
None)
318 config_vars_path = service_tmpdir /
'config_vars.yaml'
319 config_vars_text = yaml.dump(config_vars)
321 'userver fixture "service_config" writes the patched static '
322 'config vars to "%s":\n%s',
326 config_vars_path.write_text(config_vars_text)
327 config_yaml[
'config_vars'] = str(config_vars_path)
329 return _UserverConfig(config_yaml=config_yaml, config_vars=config_vars)
332@pytest.fixture(scope='session')
335 Returns a function that adjusts the static configuration file for testsuite.
336 Sets the `server.listener.port` to listen on
337 @ref pytest_userver.plugins.base.service_port "service_port" fixture value;
338 sets the `server.listener-monitor.port` to listen on
339 @ref pytest_userver.plugins.base.monitor_port "monitor_port"
342 @ingroup userver_testsuite_fixtures
345 def _patch_config(config_yaml, config_vars):
346 components = config_yaml[
'components_manager'][
'components']
347 if 'server' in components:
348 server = components[
'server']
349 if 'listener' in server:
350 server[
'listener'][
'port'] = service_port
352 if 'listener-monitor' in server:
353 server[
'listener-monitor'][
'port'] = monitor_port
358@pytest.fixture(scope='session')
361 By default, userver HTTP client is only allowed to talk to mockserver
362 when running in testsuite. This makes tests repeatable and encapsulated.
364 Override this fixture to whitelist some additional URLs.
365 It is still strongly advised to only talk to localhost in tests.
367 @ingroup userver_testsuite_fixtures
372@pytest.fixture(scope='session')
374 mockserver_info, mockserver_ssl_info, allowed_url_prefixes_extra,
377 Returns a function that adjusts the static configuration file for testsuite.
378 Sets increased timeout and limits allowed URLs for `http-client` component.
380 @ingroup userver_testsuite_fixtures
383 def patch_config(config, config_vars):
384 components: dict = config[
'components_manager'][
'components']
385 if not {
'http-client',
'testsuite-support'}.issubset(
389 http_client = components[
'http-client']
or {}
390 http_client[
'testsuite-enabled'] =
True
391 http_client[
'testsuite-timeout'] =
'10s'
393 allowed_urls = [mockserver_info.base_url]
394 if mockserver_ssl_info:
395 allowed_urls.append(mockserver_ssl_info.base_url)
396 allowed_urls += allowed_url_prefixes_extra
397 http_client[
'testsuite-allowed-url-prefixes'] = allowed_urls
402@pytest.fixture(scope='session')
405 Default log level to use in userver if no caoomand line option was provided.
409 @ingroup userver_testsuite_fixtures
414@pytest.fixture(scope='session')
417 Returns --service-log-level value if provided, otherwise returns
418 userver_default_log_level() value from fixture.
420 @ingroup userver_testsuite_fixtures
422 if pytestconfig.option.service_log_level:
423 return pytestconfig.option.service_log_level
424 return userver_default_log_level
427@pytest.fixture(scope='session')
430 Returns a function that adjusts the static configuration file for testsuite.
431 Sets the `logging.loggers.default` to log to `@stderr` with level set
432 from `--service-log-level` pytest configuration option.
434 @ingroup userver_testsuite_fixtures
437 if _service_logfile_path:
438 default_file_path = str(_service_logfile_path)
440 default_file_path =
'@stderr'
442 def _patch_config(config_yaml, config_vars):
443 components = config_yaml[
'components_manager'][
'components']
444 if 'logging' in components:
445 loggers = components[
'logging'].setdefault(
'loggers', {})
446 for logger
in loggers.values():
447 logger[
'file_path'] =
'@null'
448 loggers[
'default'] = {
449 'file_path': default_file_path,
450 'level': userver_log_level,
451 'overflow_behavior':
'discard',
453 config_vars[
'logger_level'] = userver_log_level
458@pytest.fixture(scope='session')
461 Returns a function that adjusts the static configuration file for testsuite.
462 Sets the `otlp-logger.load-enabled` to `false` to disable OTLP logging and
463 leave the default file logger.
465 @ingroup userver_testsuite_fixtures
468 def _patch_config(config_yaml, config_vars):
469 components = config_yaml[
'components_manager'][
'components']
470 if 'otlp-logger' in components:
471 components[
'otlp-logger'][
'load-enabled'] =
False
476@pytest.fixture(scope='session')
479 Returns a function that adjusts the static configuration file for testsuite.
481 Sets up `testsuite-support` component, which:
483 - increases timeouts for userver drivers
484 - disables periodic cache updates
485 - enables testsuite tasks
487 Sets the `testsuite-enabled` in config_vars.yaml to `True`; sets the
488 `tests-control.testpoint-url` to mockserver URL.
490 @ingroup userver_testsuite_fixtures
493 def _set_postgresql_options(testsuite_support: dict) ->
None:
494 testsuite_support[
'testsuite-pg-execute-timeout'] =
'35s'
495 testsuite_support[
'testsuite-pg-statement-timeout'] =
'30s'
496 testsuite_support[
'testsuite-pg-readonly-master-expected'] =
True
498 def _set_redis_timeout(testsuite_support: dict) ->
None:
499 testsuite_support[
'testsuite-redis-timeout-connect'] =
'40s'
500 testsuite_support[
'testsuite-redis-timeout-single'] =
'30s'
501 testsuite_support[
'testsuite-redis-timeout-all'] =
'30s'
503 def _disable_cache_periodic_update(testsuite_support: dict) ->
None:
504 testsuite_support[
'testsuite-periodic-update-enabled'] =
False
506 def patch_config(config, config_vars) -> None:
508 config[
'components_manager'].pop(
'graceful_shutdown_interval',
None)
509 components: dict = config[
'components_manager'][
'components']
510 if 'testsuite-support' not in components:
512 testsuite_support = components[
'testsuite-support']
or {}
513 testsuite_support[
'testsuite-increased-timeout'] =
'30s'
514 testsuite_support[
'testsuite-grpc-is-tls-enabled'] =
False
515 _set_postgresql_options(testsuite_support)
516 _set_redis_timeout(testsuite_support)
517 service_runner = pytestconfig.getoption(
'--service-runner-mode',
False)
518 if not service_runner:
519 _disable_cache_periodic_update(testsuite_support)
520 testsuite_support[
'testsuite-tasks-enabled'] =
not service_runner
521 testsuite_support[
'testsuite-periodic-dumps-enabled'] = (
522 '$userver-dumps-periodic'
524 components[
'testsuite-support'] = testsuite_support
526 config_vars[
'testsuite-enabled'] =
True
527 if 'tests-control' in components:
528 components[
'tests-control'][
'testpoint-url'] = mockserver_info.url(
535@pytest.fixture(scope='session')
538 Returns a function that adjusts the static configuration file for testsuite.
539 Sets the `default-secdist-provider.config` to the value of
540 @ref pytest_userver.plugins.config.service_secdist_path "service_secdist_path"
543 @ingroup userver_testsuite_fixtures
546 def _patch_config(config_yaml, config_vars):
547 if not service_secdist_path:
550 components = config_yaml[
'components_manager'][
'components']
551 if 'default-secdist-provider' not in components:
554 if not service_secdist_path.is_file():
556 f
'"{service_secdist_path}" is not a file. Provide a '
557 f
'"--service-secdist" pytest option or override the '
558 f
'"service_secdist_path" fixture.',
560 components[
'default-secdist-provider'][
'config'] = str(
561 service_secdist_path,
567@pytest.fixture(scope='session')
568def userver_config_testsuite_middleware(
569 userver_testsuite_middleware_enabled: bool,
571 def patch_config(config_yaml, config_vars):
572 if not userver_testsuite_middleware_enabled:
575 components = config_yaml[
'components_manager'][
'components']
576 if 'server' not in components:
579 pipeline_builder = components.setdefault(
580 'default-server-middleware-pipeline-builder', {},
582 middlewares = pipeline_builder.setdefault(
'append', [])
583 middlewares.append(
'testsuite-exceptions-handling-middleware')
588@pytest.fixture(scope='session')
590 """Enabled testsuite middleware."""