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