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 def _patch_config(config_yaml, config_vars):
439 components = config_yaml[
'components_manager'][
'components']
440 if 'logging' in components:
441 components[
'logging'][
'loggers'] = {
443 'file_path':
'@stderr',
444 'level': userver_log_level,
445 'overflow_behavior':
'discard',
448 config_vars[
'logger_level'] = userver_log_level
453@pytest.fixture(scope='session')
456 Returns a function that adjusts the static configuration file for testsuite.
458 Sets up `testsuite-support` component, which:
460 - increases timeouts for userver drivers
461 - disables periodic cache updates
462 - enables testsuite tasks
464 Sets the `testsuite-enabled` in config_vars.yaml to `True`; sets the
465 `tests-control.testpoint-url` to mockserver URL.
467 @ingroup userver_testsuite_fixtures
470 def _set_postgresql_options(testsuite_support: dict) ->
None:
471 testsuite_support[
'testsuite-pg-execute-timeout'] =
'35s'
472 testsuite_support[
'testsuite-pg-statement-timeout'] =
'30s'
473 testsuite_support[
'testsuite-pg-readonly-master-expected'] =
True
475 def _set_redis_timeout(testsuite_support: dict) ->
None:
476 testsuite_support[
'testsuite-redis-timeout-connect'] =
'40s'
477 testsuite_support[
'testsuite-redis-timeout-single'] =
'30s'
478 testsuite_support[
'testsuite-redis-timeout-all'] =
'30s'
480 def _disable_cache_periodic_update(testsuite_support: dict) ->
None:
481 testsuite_support[
'testsuite-periodic-update-enabled'] =
False
483 def patch_config(config, config_vars) -> None:
484 components: dict = config[
'components_manager'][
'components']
485 if 'testsuite-support' not in components:
487 testsuite_support = components[
'testsuite-support']
or {}
488 testsuite_support[
'testsuite-increased-timeout'] =
'30s'
489 _set_postgresql_options(testsuite_support)
490 _set_redis_timeout(testsuite_support)
491 service_runner = pytestconfig.getoption(
'--service-runner-mode',
False)
492 if not service_runner:
493 _disable_cache_periodic_update(testsuite_support)
494 testsuite_support[
'testsuite-tasks-enabled'] =
not service_runner
496 'testsuite-periodic-dumps-enabled'
497 ] =
'$userver-dumps-periodic'
498 components[
'testsuite-support'] = testsuite_support
500 config_vars[
'testsuite-enabled'] =
True
501 if 'tests-control' in components:
502 components[
'tests-control'][
'testpoint-url'] = mockserver_info.url(
509@pytest.fixture(scope='session')
512 Returns a function that adjusts the static configuration file for testsuite.
513 Sets the `default-secdist-provider.config` to the value of
514 @ref pytest_userver.plugins.config.service_secdist_path "service_secdist_path"
517 @ingroup userver_testsuite_fixtures
520 def _patch_config(config_yaml, config_vars):
521 if not service_secdist_path:
524 components = config_yaml[
'components_manager'][
'components']
525 if 'default-secdist-provider' not in components:
528 if not service_secdist_path.is_file():
530 f
'"{service_secdist_path}" is not a file. Provide a '
531 f
'"--service-secdist" pytest option or override the '
532 f
'"service_secdist_path" fixture.',
534 components[
'default-secdist-provider'][
'config'] = str(
535 service_secdist_path,
541@pytest.fixture(scope='session')
542def userver_config_testsuite_middleware(
543 userver_testsuite_middleware_enabled: bool,
545 def patch_config(config_yaml, config_vars):
546 if not userver_testsuite_middleware_enabled:
549 components = config_yaml[
'components_manager'][
'components']
550 if 'server' not in components:
553 pipeline_builder = components.setdefault(
554 'default-server-middleware-pipeline-builder', {},
556 middlewares = pipeline_builder.setdefault(
'append', [])
557 middlewares.append(
'testsuite-exceptions-handling-middleware')
562@pytest.fixture(scope='session')
564 """Enabled testsuite middleware."""