userver: en/testsuite/environment/main.py Source File
Loading...
Searching...
No Matches
main.py
1import argparse
2import contextlib
3import importlib
4import logging
5import pathlib
6import subprocess
7import sys
8
9from testsuite.utils import colors
10
11from . import control, shell, utils
12
13DEFAULT_SERVICE_PLUGINS = [
14 'testsuite.databases.mongo.pytest_plugin',
15 'testsuite.databases.pgsql.pytest_plugin',
16 'testsuite.databases.redis.pytest_plugin',
17 'testsuite.databases.mysql.pytest_plugin',
18 'testsuite.databases.clickhouse.pytest_plugin',
19 'testsuite.databases.rabbitmq.pytest_plugin',
20 'testsuite.databases.kafka.pytest_plugin',
21]
22
23logger = logging.getLogger(__name__)
24
25
26class ColoredLevelFormatter(logging.Formatter):
27 LEVEL_COLORS = {
28 logging.DEBUG: colors.Colors.GRAY,
29 logging.INFO: colors.Colors.BRIGHT_GREEN,
30 logging.WARNING: colors.Colors.YELLOW,
31 logging.ERROR: colors.Colors.RED,
32 logging.CRITICAL: colors.Colors.BRIGHT_RED,
33 }
34
35 def __init__(self, *, colors_enabled=False):
36 super().__init__()
37 self._colors_enabled = colors_enabled
38
39 def format(self, record: logging.LogRecord):
40 message = super().format(record)
41 if not self._colors_enabled:
42 return f'{record.levelname} {message}'
43 color = self.LEVEL_COLORS.get(record.levelno, colors.Colors.DEFAULT)
44 return f'{color}{record.levelname}{colors.Colors.DEFAULT} {message}'
45
46
47def csv_arg(value: str):
48 result = []
49 for arg in value.split(','):
50 arg = arg.strip()
51 if arg:
52 result.append(arg)
53 return result
54
55
56def main(args=None, service_plugins=None):
57 utils.ensure_non_root_user()
58 testsuite_services = _register_services(service_plugins)
59 default_services = sorted(testsuite_services.keys())
60
61 parser = argparse.ArgumentParser()
62 parser.add_argument(
63 '--env-dir',
64 help='Path environment data directry.',
65 type=pathlib.Path,
66 default=None,
67 )
68 parser.add_argument(
69 '-f',
70 '--force',
71 action='store_true',
72 help='Force run, ignore failures',
73 )
74 parser.add_argument(
75 '--reuse-services',
76 action='store_true',
77 help='Do not re-raise already running services',
78 )
79 services_group = parser.add_mutually_exclusive_group()
80 services_group.add_argument(
81 '-s',
82 '--databases',
83 dest='services',
84 type=csv_arg,
85 help='Comma separated list of services (default: %(default)s)',
86 default=default_services,
87 )
88 services_group.add_argument(
89 '--services',
90 nargs='+',
91 help='Deprecated! List of services (default: %(default)s)',
92 default=default_services,
93 )
94 parser.add_argument(
95 '--log-level',
96 choices=['debug', 'info', 'warning', 'error', 'critical'],
97 default='debug',
98 )
99 parser.set_defaults(handler=None)
100
101 subparsers = parser.add_subparsers(metavar='command')
102
103 command_parser = subparsers.add_parser('start', help='Start services')
104 command_parser.set_defaults(handler=_command_start)
105
106 command_parser = subparsers.add_parser('stop', help='Stop services')
107 command_parser.set_defaults(handler=_command_stop)
108
109 command_parser = subparsers.add_parser(
110 'run',
111 help='Run command with services started',
112 )
113 command_parser.add_argument('command', nargs='+', help='Command to run')
114 command_parser.set_defaults(handler=_command_run)
115
116 args = parser.parse_args(args=args)
117 if args.handler is None:
118 parser.error('the following arguments are required: command')
119
120 _setup_logging(args.log_level.upper())
121
122 config = control.load_environment_config(
123 env_dir=args.env_dir,
124 reuse_services=args.reuse_services,
125 verbose=2,
126 )
127 env = control.TestsuiteEnvironment(config)
128 for service_name, service_class in testsuite_services.items():
129 env.register_service(service_name, service_class)
130 args.handler(env, args)
131
132
133def _setup_logging(log_level):
134 root_logger = logging.getLogger()
135 root_logger.setLevel(log_level)
136 handler = logging.StreamHandler(sys.stderr)
137 handler.setFormatter(
139 colors_enabled=sys.stderr.isatty(),
140 ),
141 )
142 root_logger.addHandler(handler)
143
144
145def _command_start(env, args):
146 status = True
147 for service_name in args.services:
148 try:
149 env.ensure_started(service_name)
150 except shell.SubprocessFailed as exc:
151 logger.error(
152 'Failed to start service %s: %s',
153 service_name,
154 str(exc),
155 )
156 status = False
157 if not args.force:
158 break
159 if not status:
160 sys.exit(1)
161
162
163def _command_stop(env, args):
164 status = True
165 for service_name in args.services:
166 try:
167 env.stop_service(service_name)
168 except shell.SubprocessFailed as exc:
169 logger.error(
170 'Failed to stop service %s: %s',
171 service_name,
172 str(exc),
173 )
174 status = False
175 if not args.force:
176 break
177 if not status:
178 sys.exit(1)
179
180
181def _command_run(env, args):
182 _command_start(env, args)
183 with contextlib.closing(env):
184 exit_code = subprocess.call(args.command)
185 sys.exit(exit_code)
186
187
188def _register_services(service_plugins=None):
189 services = {}
190
191 def _register_service(name, factory=None):
192 def decorator(factory):
193 services[name] = factory
194
195 if factory is None:
196 return decorator
197 return decorator(factory)
198
199 if service_plugins is None:
200 service_plugins = DEFAULT_SERVICE_PLUGINS
201 strict_check = False
202 else:
203 strict_check = True
204
205 for modname in service_plugins:
206 try:
207 mod = importlib.import_module(modname)
208 except ImportError:
209 if strict_check:
210 raise
211 continue
212 mod.pytest_service_register(register_service=_register_service)
213 return services
214
215
216if __name__ == '__main__':
217 main()