2import concurrent.futures
12from testsuite.environment
import shell
14from pytest_userver
import sql
19if hasattr(yaml,
'CLoader'):
20 _YamlLoader = yaml.CLoader
22 _YamlLoader = yaml.Loader
24USERVER_CONFIG_HOOKS = [
'userver_config_ydb']
28def ydb(_ydb_client, _ydb_init) -> client.YdbClient:
32 @ingroup userver_testsuite_fixtures
37@pytest.fixture(scope='session')
38def _ydb_client(_ydb_client_pool):
39 with _ydb_client_pool()
as ydb_client:
43@pytest.fixture(scope='session')
44def _ydb_client_pool(_ydb_service, ydb_service_settings):
45 endpoint =
'{}:{}'.format(
46 ydb_service_settings.host,
47 ydb_service_settings.grpc_port,
51 @contextlib.contextmanager
54 ydb_client = pool.pop()
56 ydb_client = client.YdbClient(
58 ydb_service_settings.database,
63 pool.append(ydb_client)
68def pytest_service_register(register_service):
69 register_service(
'ydb', service.create_ydb_service)
72@pytest.fixture(scope='session')
73def _ydb_service(pytestconfig, ensure_service_started, ydb_service_settings):
74 if os.environ.get(
'YDB_ENDPOINT')
or pytestconfig.option.ydb_host:
76 ensure_service_started(
'ydb', settings=ydb_service_settings)
79@pytest.fixture(scope='session')
80def ydb_service_settings(pytestconfig) -> service.ServiceSettings:
81 endpoint_from_env = os.environ.get(
'YDB_ENDPOINT')
82 database = os.environ.get(
'YDB_DATABASE',
'local')
85 host, grpc_port = endpoint_from_env.split(
':', 1)
86 return service.ServiceSettings(
95 if pytestconfig.option.ydb_host:
96 return service.ServiceSettings(
97 host=pytestconfig.option.ydb_host,
98 grpc_port=pytestconfig.option.ydb_grpc_port,
99 mon_port=pytestconfig.option.ydb_mon_port,
100 ic_port=pytestconfig.option.ydb_ic_port,
102 wait_time=pytestconfig.option.ydb_wait_time,
104 settings = service.get_service_settings()
105 return dataclasses.replace(settings, wait_time=pytestconfig.option.ydb_wait_time)
108@pytest.fixture(scope='session')
109def _ydb_service_schemas(service_source_dir):
110 service_schemas_ydb = service_source_dir /
'ydb' /
'schemas'
111 return discover.find_schemas([service_schemas_ydb])
114@pytest.fixture(scope='session')
115def ydb_settings_substitute(ydb_service_settings):
116 def secdist_settings(*args, **kwargs):
118 'endpoint':
'{}:{}'.format(
119 ydb_service_settings.host,
120 ydb_service_settings.grpc_port,
122 'database':
'/{}'.format(ydb_service_settings.database),
126 return {
'ydb_settings': secdist_settings}
129@pytest.fixture(scope='session')
139@pytest.fixture(scope='session')
140def ydb_migration_dir(service_source_dir) -> pathlib.Path:
142 Directory with migration files
144 @ingroup userver_testsuite_fixtures
146 return service_source_dir /
'ydb' /
'migrations'
149YDB_MIGRATION_TABLE =
'goose_db_version'
152def _ydb_migrate(ydb_service_settings, ydb_migration_dir, goose_binary_path):
153 if not ydb_migration_dir.exists():
155 if not list(ydb_migration_dir.iterdir()):
158 host = ydb_service_settings.host
159 port = ydb_service_settings.grpc_port
162 str(goose_binary_path),
164 str(ydb_migration_dir),
168 (f
'grpc://{host}:{port}/local?go_query_mode=scripting&go_fake_tx=scripting&go_query_bind=declare,numeric'),
172 shell.execute(command, verbose=
True, command_alias=
'ydb/migrations')
173 except shell.SubprocessFailed
as exc:
174 raise Exception(f
'YDB run migration failed:\n{exc}')
177@pytest.fixture(scope='session')
178def goose_binary_path() -> pathlib.Path:
180 Path to 'goose' migration tool.
182 Override this fixture to change the way 'goose' binary is discovered.
184 @ingroup userver_testsuite_fixtures
189 return yatest.common.runtime.binary_path(
190 'contrib/go/patched/goose/cmd/goose/goose',
196def _ydb_fetch_table_names(ydb_service_settings, ydb_cli) -> list[str]:
198 host = ydb_service_settings.host
199 port = ydb_service_settings.grpc_port
200 output = subprocess.check_output(
204 f
'grpc://{host}:{port}',
215 for line
in output.split(
'\n'):
216 if ' table ' not in line:
220 if YDB_MIGRATION_TABLE
in line:
222 path = line.split(
'│')[6].strip()
225 except subprocess.CalledProcessError
as exc:
226 raise Exception(f
'Could not fetch table names:\n{exc}')
229@pytest.fixture(scope='session')
230def ydb_cli() -> pathlib.Path:
232 Path to YDB CLI executable.
234 Override this fixture to change the way YDB CLI is discovered.
236 @ingroup userver_testsuite_fixtures
241 return yatest.common.runtime.binary_path(
'contrib/ydb/apps/ydb/ydb')
246@pytest.fixture(scope='session')
249 _ydb_service_schemas,
250 ydb_service_settings,
255 if _ydb_service_schemas
and ydb_migration_dir.exists():
257 'Both ydb/schema and ydb/migrations exist, which are mutually exclusive',
261 for schema_path
in _ydb_service_schemas:
262 with open(schema_path)
as fp:
263 tables_schemas = yaml.load(fp.read(), Loader=_YamlLoader)
264 for table_schema
in tables_schemas:
265 client.drop_table(_ydb_client, table_schema[
'path'])
266 client.create_table(_ydb_client, table_schema)
267 _ydb_state.tables.append(table_schema[
'path'])
270 _ydb_migrate(ydb_service_settings, ydb_migration_dir, goose_binary_path)
272 _ydb_state.init =
True
275@pytest.fixture(scope='session')
276def _ydb_tables(_ydb_state, _ydb_prepare, ydb_service_settings, ydb_cli):
279 *_ydb_fetch_table_names(ydb_service_settings, ydb_cli),
281 return tuple(sorted(tables))
289 ydb_service_settings,
295 def ydb_mark_queries(files=(), queries=()):
298 result_queries.append(load(path))
299 result_queries.extend(queries)
300 return result_queries
302 def drop_table(table):
303 with _ydb_client_pool()
as ydb_client:
304 ydb_client.execute(
'DELETE FROM `{}`'.format(table))
307 with concurrent.futures.ThreadPoolExecutor(
308 max_workers=len(_ydb_tables),
310 executer.map(drop_table, _ydb_tables)
312 for mark
in request.node.iter_markers(
'ydb'):
313 queries = ydb_mark_queries(**mark.kwargs)
314 for query
in queries:
315 _ydb_client.execute(query)
319def userver_ydb_trx(testpoint) -> sql.RegisteredTrx:
321 The fixture maintains transaction fault injection state using
324 @see pytest_userver.sql.RegisteredTrx
326 @snippet integration_tests/tests/test_trx_failure.py fault injection
328 @ingroup userver_testsuite_fixtures
331 registered = sql.RegisteredTrx()
333 @testpoint('ydb_trx_commit')
334 def _pg_trx_tp(data):
335 should_fail = registered.is_failure_enabled(data[
'trx_name'])
336 return {
'trx_should_fail': should_fail}
341@pytest.fixture(scope='session')
342def userver_config_ydb(ydb_service_settings):
344 Returns a function that adjusts the static configuration file for testsuite.
346 For all `ydb.databases`, sets `endpoint` and `database` to the local test
349 @ingroup userver_testsuite_fixtures
352 endpoint = f
'{ydb_service_settings.host}:{ydb_service_settings.grpc_port}'
353 database = (
'' if ydb_service_settings.database.startswith(
'/')
else '/') + ydb_service_settings.database
355 def patch_config(config, config_vars):
356 ydb_component = config[
'components_manager'][
'components'][
'ydb']
357 if isinstance(ydb_component, str):
358 ydb_component = config_vars[ydb_component[1:]]
359 databases = ydb_component[
'databases']
360 for dbconfig
in databases.values():
361 dbconfig[
'endpoint'] = endpoint
362 dbconfig[
'database'] = database