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_logging_otlp',
37 'userver_config_testsuite',
38 'userver_config_secdist',
39 'userver_config_testsuite_middleware',
46logger = logging.getLogger(__name__)
49class _UserverConfigPlugin:
51 self._config_hooks = []
54 def userver_config_hooks(self):
55 return self._config_hooks
57 def pytest_plugin_registered(self, plugin, manager):
58 if not isinstance(plugin, types.ModuleType):
60 uhooks = getattr(plugin,
'USERVER_CONFIG_HOOKS',
None)
61 if uhooks
is not None:
62 self._config_hooks.extend(uhooks)
65class _UserverConfig(typing.NamedTuple):
70def pytest_configure(config):
71 config.pluginmanager.register(_UserverConfigPlugin(),
'userver_config')
74def pytest_addoption(parser) -> None:
75 group = parser.getgroup(
'userver-config')
77 '--service-log-level',
79 choices=[
'trace',
'debug',
'info',
'warning',
'error',
'critical'],
84 help=
'Path to service.yaml file.',
87 '--service-config-vars',
89 help=
'Path to config_vars.yaml file.',
94 help=
'Path to secure_data.json file.',
99 help=
'Path to dynamic config fallback file.',
104 help=
'Dump config from binary before running tests',
111@pytest.fixture(scope='session')
114 Returns the path to service.yaml file set by command line
115 `--service-config` option.
117 Override this fixture to change the way path to service.yaml is provided.
119 @ingroup userver_testsuite_fixtures
121 if pytestconfig.option.dump_config:
125 pytestconfig.option.service_config,
127 return pytestconfig.option.service_config
130@pytest.fixture(scope='session')
133 Runs the service binary with `--dump-db-schema` argument, dumps the 0_db_schema.sql file with database schema and
136 Override this fixture to change the way to dump the database schema.
138 @ingroup userver_testsuite_fixtures
141 path = service_tmpdir.joinpath(
'schemas')
146 path /
'0_db_schema.sql',
151@pytest.fixture(scope='session')
154 Returns the path to config_vars.yaml file set by command line
155 `--service-config-vars` option.
157 Override this fixture to change the way path to config_vars.yaml is
160 @ingroup userver_testsuite_fixtures
162 return pytestconfig.option.service_config_vars
165@pytest.fixture(scope='session')
168 Returns the path to secure_data.json file set by command line
169 `--service-secdist` option.
171 Override this fixture to change the way path to secure_data.json is
174 @ingroup userver_testsuite_fixtures
176 return pytestconfig.option.service_secdist
179@pytest.fixture(scope='session')
182 Returns the path to dynamic config fallback file set by command line
183 `--config-fallback` option.
185 Override this fixture to change the way path to dynamic config fallback is
188 @ingroup userver_testsuite_fixtures
190 return pytestconfig.option.config_fallback
193@pytest.fixture(scope='session')
196 Returns the path for temporary files. The path is the same for the whole
197 session and files are not removed (at least by this fixture) between
200 @ingroup userver_testsuite_fixtures
202 return tmp_path_factory.mktemp(
203 pathlib.Path(service_binary).name, numbered=
False,
207@pytest.fixture(scope='session')
209 service_tmpdir, service_config, service_config_yaml,
212 Dumps the contents of the service_config_yaml into a static config for
213 testsuite and returns the path to the config file.
215 @ingroup userver_testsuite_fixtures
217 dst_path = service_tmpdir /
'config.yaml'
220 'userver fixture "service_config_path_temp" writes the patched static '
221 'config to "%s" equivalent to:\n%s',
223 yaml.dump(service_config),
225 dst_path.write_text(yaml.dump(service_config_yaml))
230@pytest.fixture(scope='session')
233 Returns the static config values after the USERVER_CONFIG_HOOKS were
234 applied (if any). Prefer using
235 pytest_userver.plugins.config.service_config
237 @ingroup userver_testsuite_fixtures
239 return _service_config_hooked.config_yaml
242@pytest.fixture(scope='session')
245 Returns the static config variables (config_vars.yaml) values after the
246 USERVER_CONFIG_HOOKS were applied (if any). Prefer using
247 pytest_userver.plugins.config.service_config
249 @ingroup userver_testsuite_fixtures
251 return _service_config_hooked.config_vars
254def _substitute_values(config, service_config_vars: dict, service_env) ->
None:
255 if isinstance(config, dict):
256 for key, value
in config.items():
257 if not isinstance(value, str):
258 _substitute_values(value, service_config_vars, service_env)
261 if not value.startswith(
'$'):
264 new_value = service_config_vars.get(value[1:])
265 if new_value
is not None:
266 config[key] = new_value
269 env = config.get(f
'{key}#env')
272 new_value = service_env.get(service_env)
274 new_value = os.environ.get(env)
276 config[key] = new_value
279 fallback = config.get(f
'{key}#fallback')
281 config[key] = fallback
283 if isinstance(config, list):
284 for i, value
in enumerate(config):
285 if not isinstance(value, str):
286 _substitute_values(value, service_config_vars, service_env)
289 if not value.startswith(
'$'):
292 new_value = service_config_vars.get(value[1:])
293 if new_value
is not None:
294 config[i] = new_value
297@pytest.fixture(scope='session')
299 service_config_yaml, service_config_vars, service_env,
302 Returns the static config values after the USERVER_CONFIG_HOOKS were
303 applied (if any) and with all the '$', environment and fallback variables
306 @ingroup userver_testsuite_fixtures
308 config = copy.deepcopy(service_config_yaml)
309 _substitute_values(config, service_config_vars, service_env)
310 config.pop(
'config_vars',
None)
314@pytest.fixture(scope='session')
315def _original_service_config(
316 service_config_path, service_config_vars_path,
321 with open(service_config_path, mode=
'rt')
as fp:
322 config_yaml = yaml.safe_load(fp)
324 if service_config_vars_path:
325 with open(service_config_vars_path, mode=
'rt')
as fp:
326 config_vars = yaml.safe_load(fp)
330 return _UserverConfig(config_yaml=config_yaml, config_vars=config_vars)
333@pytest.fixture(scope='session')
334def _service_config_hooked(
335 pytestconfig, request, service_tmpdir, _original_service_config,
337 config_yaml = copy.deepcopy(_original_service_config.config_yaml)
338 config_vars = copy.deepcopy(_original_service_config.config_vars)
340 plugin = pytestconfig.pluginmanager.get_plugin(
'userver_config')
341 for hook
in plugin.userver_config_hooks:
342 if not callable(hook):
343 hook_func = request.getfixturevalue(hook)
346 hook_func(config_yaml, config_vars)
349 config_yaml.pop(
'config_vars',
None)
351 config_vars_path = service_tmpdir /
'config_vars.yaml'
352 config_vars_text = yaml.dump(config_vars)
354 'userver fixture "service_config" writes the patched static '
355 'config vars to "%s":\n%s',
359 config_vars_path.write_text(config_vars_text)
360 config_yaml[
'config_vars'] = str(config_vars_path)
362 return _UserverConfig(config_yaml=config_yaml, config_vars=config_vars)
365@pytest.fixture(scope='session')
368 Returns a function that adjusts the static configuration file for testsuite.
369 Sets the `server.listener.port` to listen on
370 @ref pytest_userver.plugins.base.service_port "service_port" fixture value;
371 sets the `server.listener-monitor.port` to listen on
372 @ref pytest_userver.plugins.base.monitor_port "monitor_port"
375 @ingroup userver_testsuite_fixtures
378 def _patch_config(config_yaml, config_vars):
379 components = config_yaml[
'components_manager'][
'components']
380 if 'server' in components:
381 server = components[
'server']
382 if 'listener' in server:
383 server[
'listener'][
'port'] = service_port
385 if 'listener-monitor' in server:
386 server[
'listener-monitor'][
'port'] = monitor_port
391@pytest.fixture(scope='session')
394 By default, userver HTTP client is only allowed to talk to mockserver
395 when running in testsuite. This makes tests repeatable and encapsulated.
397 Override this fixture to whitelist some additional URLs.
398 It is still strongly advised to only talk to localhost in tests.
400 @ingroup userver_testsuite_fixtures
405@pytest.fixture(scope='session')
407 mockserver_info, mockserver_ssl_info, allowed_url_prefixes_extra,
410 Returns a function that adjusts the static configuration file for testsuite.
411 Sets increased timeout and limits allowed URLs for `http-client` component.
413 @ingroup userver_testsuite_fixtures
416 def patch_config(config, config_vars):
417 components: dict = config[
'components_manager'][
'components']
418 if not {
'http-client',
'testsuite-support'}.issubset(
422 http_client = components[
'http-client']
or {}
423 http_client[
'testsuite-enabled'] =
True
424 http_client[
'testsuite-timeout'] =
'10s'
426 allowed_urls = [mockserver_info.base_url]
427 if mockserver_ssl_info:
428 allowed_urls.append(mockserver_ssl_info.base_url)
429 allowed_urls += allowed_url_prefixes_extra
430 http_client[
'testsuite-allowed-url-prefixes'] = allowed_urls
435@pytest.fixture(scope='session')
438 Default log level to use in userver if no caoomand line option was provided.
442 @ingroup userver_testsuite_fixtures
447@pytest.fixture(scope='session')
450 Returns --service-log-level value if provided, otherwise returns
451 userver_default_log_level() value from fixture.
453 @ingroup userver_testsuite_fixtures
455 if pytestconfig.option.service_log_level:
456 return pytestconfig.option.service_log_level
457 return userver_default_log_level
460@pytest.fixture(scope='session')
463 Returns a function that adjusts the static configuration file for testsuite.
464 Sets the `logging.loggers.default` to log to `@stderr` with level set
465 from `--service-log-level` pytest configuration option.
467 @ingroup userver_testsuite_fixtures
470 if _service_logfile_path:
471 default_file_path = str(_service_logfile_path)
473 default_file_path =
'@stderr'
475 def _patch_config(config_yaml, config_vars):
476 components = config_yaml[
'components_manager'][
'components']
477 if 'logging' in components:
478 loggers = components[
'logging'].setdefault(
'loggers', {})
479 for logger
in loggers.values():
480 logger[
'file_path'] =
'@null'
481 loggers[
'default'] = {
482 'file_path': default_file_path,
483 'level': userver_log_level,
484 'overflow_behavior':
'discard',
486 config_vars[
'logger_level'] = userver_log_level
491@pytest.fixture(scope='session')
494 Returns a function that adjusts the static configuration file for testsuite.
495 Sets the `otlp-logger.load-enabled` to `false` to disable OTLP logging and
496 leave the default file logger.
498 @ingroup userver_testsuite_fixtures
501 def _patch_config(config_yaml, config_vars):
502 components = config_yaml[
'components_manager'][
'components']
503 if 'otlp-logger' in components:
504 components[
'otlp-logger'][
'load-enabled'] =
False
509@pytest.fixture(scope='session')
512 Returns a function that adjusts the static configuration file for testsuite.
514 Sets up `testsuite-support` component, which:
516 - increases timeouts for userver drivers
517 - disables periodic cache updates
518 - enables testsuite tasks
520 Sets the `testsuite-enabled` in config_vars.yaml to `True`; sets the
521 `tests-control.testpoint-url` to mockserver URL.
523 @ingroup userver_testsuite_fixtures
526 def _set_postgresql_options(testsuite_support: dict) ->
None:
527 testsuite_support[
'testsuite-pg-execute-timeout'] =
'35s'
528 testsuite_support[
'testsuite-pg-statement-timeout'] =
'30s'
529 testsuite_support[
'testsuite-pg-readonly-master-expected'] =
True
531 def _set_redis_timeout(testsuite_support: dict) ->
None:
532 testsuite_support[
'testsuite-redis-timeout-connect'] =
'40s'
533 testsuite_support[
'testsuite-redis-timeout-single'] =
'30s'
534 testsuite_support[
'testsuite-redis-timeout-all'] =
'30s'
536 def _disable_cache_periodic_update(testsuite_support: dict) ->
None:
537 testsuite_support[
'testsuite-periodic-update-enabled'] =
False
539 def patch_config(config, config_vars) -> None:
541 config[
'components_manager'].pop(
'graceful_shutdown_interval',
None)
542 components: dict = config[
'components_manager'][
'components']
543 if 'testsuite-support' not in components:
545 testsuite_support = components[
'testsuite-support']
or {}
546 testsuite_support[
'testsuite-increased-timeout'] =
'30s'
547 testsuite_support[
'testsuite-grpc-is-tls-enabled'] =
False
548 _set_postgresql_options(testsuite_support)
549 _set_redis_timeout(testsuite_support)
550 service_runner = pytestconfig.getoption(
'--service-runner-mode',
False)
551 if not service_runner:
552 _disable_cache_periodic_update(testsuite_support)
553 testsuite_support[
'testsuite-tasks-enabled'] =
not service_runner
554 testsuite_support[
'testsuite-periodic-dumps-enabled'] = (
555 '$userver-dumps-periodic'
557 components[
'testsuite-support'] = testsuite_support
559 config_vars[
'testsuite-enabled'] =
True
560 if 'tests-control' in components:
561 components[
'tests-control'][
'testpoint-url'] = mockserver_info.url(
568@pytest.fixture(scope='session')
571 Returns a function that adjusts the static configuration file for testsuite.
572 Sets the `default-secdist-provider.config` to the value of
573 @ref pytest_userver.plugins.config.service_secdist_path "service_secdist_path"
576 @ingroup userver_testsuite_fixtures
579 def _patch_config(config_yaml, config_vars):
580 if not service_secdist_path:
583 components = config_yaml[
'components_manager'][
'components']
584 if 'default-secdist-provider' not in components:
587 if not service_secdist_path.is_file():
589 f
'"{service_secdist_path}" is not a file. Provide a '
590 f
'"--service-secdist" pytest option or override the '
591 f
'"service_secdist_path" fixture.',
593 components[
'default-secdist-provider'][
'config'] = str(
594 service_secdist_path,
600@pytest.fixture(scope='session')
601def userver_config_testsuite_middleware(
602 userver_testsuite_middleware_enabled: bool,
604 def patch_config(config_yaml, config_vars):
605 if not userver_testsuite_middleware_enabled:
608 components = config_yaml[
'components_manager'][
'components']
609 if 'server' not in components:
612 pipeline_builder = components.setdefault(
613 'default-server-middleware-pipeline-builder', {},
615 middlewares = pipeline_builder.setdefault(
'append', [])
616 middlewares.append(
'testsuite-exceptions-handling-middleware')
621@pytest.fixture(scope='session')
623 """Enabled testsuite middleware."""