userver: /data/code/service_template/third_party/userver/testsuite/pytest_plugins/pytest_userver/plugins/service.py Source File
Loading...
Searching...
No Matches
service.py
1"""
2Start the service in testsuite.
3"""
4
5import logging
6import pathlib
7import sys
8import traceback
9import typing
10
11import pytest
12
13from testsuite.logging import logger
14from testsuite.utils import url_util
15
16from ..utils import colorize
17from ..utils import net
18
19
20logger_testsuite = logging.getLogger(__name__)
21
22
23class ColorLogger(logger.Logger):
24 def __init__(
25 self, *, writer: logger.LineLogger, verbose, colors_enabled,
26 ) -> None:
27 super().__init__(writer)
28 self._colorizer = colorize.Colorizer(
29 verbose=verbose, colors_enabled=colors_enabled,
30 )
31
32 def log_service_line(self, line) -> None:
33 line = self._colorizer.colorize_line(line)
34 if line:
35 self.writeline(line)
36
37 def log_entry(self, entry: dict) -> None:
38 line = self._colorizer.colorize_row(entry)
39 if line:
40 self.writeline(line)
41
42
43def pytest_addoption(parser) -> None:
44 group = parser.getgroup('userver')
45 group.addoption(
46 '--service-logs-file',
47 type=pathlib.Path,
48 help='Write service output to specified file',
49 )
50 group.addoption(
51 '--service-logs-pretty',
52 action='store_true',
53 help='Enable pretty print and colorize service logs',
54 )
55 group.addoption(
56 '--service-logs-pretty-verbose',
57 dest='service_logs_pretty',
58 action='store_const',
59 const='verbose',
60 help='Enable pretty print and colorize service logs in verbose mode',
61 )
62 group.addoption(
63 '--service-logs-pretty-disable',
64 action='store_false',
65 dest='service_logs_pretty',
66 help='Disable pretty print and colorize service logs',
67 )
68
69
70def pytest_override_testsuite_logger(
71 config, line_logger: logger.LineLogger, colors_enabled: bool,
72) -> typing.Optional[logger.Logger]:
73 pretty_logs = config.option.service_logs_pretty
74 if not pretty_logs:
75 return None
76 return ColorLogger(
77 writer=line_logger,
78 verbose=pretty_logs == 'verbose',
79 colors_enabled=colors_enabled,
80 )
81
82
83@pytest.fixture(scope='session')
85 """
86 Override this to pass extra environment variables to the service.
87
88 @snippet samples/redis_service/tests/conftest.py service_env
89 @ingroup userver_testsuite_fixtures
90 """
91 return None
92
93
94@pytest.fixture(scope='session')
96 service_config_yaml, service_baseurl,
97) -> typing.Optional[str]:
98 """
99 Returns the service HTTP ping URL that is used by the testsuite to detect
100 that the service is ready to work. Returns None if there's no such URL.
101
102 By default attempts to find server::handlers::Ping component by
103 "handler-ping" name in static config. Override this fixture to change the
104 behavior.
105
106 @ingroup userver_testsuite_fixtures
107 """
108 components = service_config_yaml['components_manager']['components']
109 ping_handler = components.get('handler-ping')
110 if ping_handler:
111 return url_util.join(service_baseurl, ping_handler['path'])
112 return None
113
114
115@pytest.fixture(scope='session')
116def service_non_http_health_checks(service_config_yaml) -> net.HealthChecks:
117 """
118 Returns a health checks info.
119
120 By default returns pytest_userver.utils.net.get_health_checks_info().
121
122 Override this fixture to change the way testsuite detects the tested
123 service being alive.
124
125 @ingroup userver_testsuite_fixtures
126 """
127
128 return net.get_health_checks_info(service_config_yaml)
129
130
131@pytest.fixture(scope='session')
133 pytestconfig,
134 create_daemon_scope,
135 service_env,
136 service_http_ping_url,
137 service_config_path_temp,
138 service_config_yaml,
139 service_binary,
140 service_non_http_health_checks,
141 testsuite_logger,
142 _userver_log_handler,
143):
144 """
145 Configures the health checking to use service_http_ping_url fixture value
146 if it is not None; otherwise uses the service_non_http_health_checks info.
147 Starts the service daemon.
148
149 @ingroup userver_testsuite_fixtures
150 """
151 assert service_http_ping_url or service_non_http_health_checks.tcp, (
152 '"service_http_ping_url" and "create_health_checker" fixtures '
153 'returned None. Testsuite is unable to detect if the service is ready '
154 'to accept requests.',
155 )
156
157 logger_testsuite.debug(
158 'userver fixture "service_daemon" would check for "%s"',
159 service_non_http_health_checks,
160 )
161
162 async def _checker(*, session, process) -> bool:
163 logger_testsuite.debug(
164 'userver fixture "service_daemon" is about to start "%s" checks',
165 service_non_http_health_checks,
166 )
167 result = await net.check_availability(service_non_http_health_checks)
168 logger_testsuite.debug(
169 'userver fixture "service_daemon" checked "%s" and got "%s"',
170 service_non_http_health_checks,
171 result,
172 )
173 return result
174
175 health_check = _checker
176 if service_http_ping_url:
177 health_check = None
178
179 async with create_daemon_scope(
180 args=[
181 str(service_binary),
182 '--config',
183 str(service_config_path_temp),
184 ],
185 ping_url=service_http_ping_url,
186 health_check=health_check,
187 env=service_env,
188 stderr_handler=_userver_log_handler,
189 ) as scope:
190 yield scope
191
192
193@pytest.fixture(scope='session')
194def _userver_log_handler(pytestconfig, testsuite_logger, _uservice_logfile):
195 service_logs_pretty = pytestconfig.option.service_logs_pretty
196 if not service_logs_pretty and not bool(_uservice_logfile):
197 return None
198
199 if service_logs_pretty:
200 logger_plugin = pytestconfig.pluginmanager.getplugin(
201 'testsuite_logger',
202 )
203 logger_plugin.enable_logs_suspension()
204
205 def log_handler(line_binary):
206 if _uservice_logfile:
207 _uservice_logfile.write(line_binary)
208 try:
209 line = line_binary.decode('utf-8').rstrip('\r\n')
210 testsuite_logger.log_service_line(line)
211 # flake8: noqa
212 except:
213 traceback.print_exc(file=sys.stderr)
214
215 return log_handler
216
217
218@pytest.fixture(scope='session')
219def _uservice_logfile(pytestconfig):
220 path = pytestconfig.option.service_logs_file
221 if not path:
222 yield None
223 else:
224 with path.open('wb') as fp:
225 yield fp