mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-24 18:40:38 +00:00
Add the possibility to run boost test from pytest. Boost facade based on code from https://github.com/pytest-dev/pytest-cpp, but enhanced and rewritten to suite better.
163 lines
6.3 KiB
Python
163 lines
6.3 KiB
Python
#
|
|
# Copyright (C) 2025-present ScyllaDB
|
|
#
|
|
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
|
#
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
import shutil
|
|
import time
|
|
from contextlib import contextmanager
|
|
from pathlib import Path
|
|
from typing import Any, Generator
|
|
|
|
from test.pylib.host_registry import HostRegistry
|
|
from test.pylib.minio_server import MinioServer
|
|
from test.pylib.s3_proxy import S3ProxyServer
|
|
from test.pylib.s3_server_mock import MockS3Server
|
|
from test.pylib.util import LogPrefixAdapter
|
|
|
|
|
|
class PrepareChildProcessEnv:
|
|
"""
|
|
Class responsible to get environment variables from the main thread through the shared file and set them for the process
|
|
"""
|
|
def __init__(self, env_file: Path):
|
|
self.env_file = env_file
|
|
|
|
def prepare(self) -> None:
|
|
"""
|
|
Read the environment variables for S3 and MinIO from the file and set them for the process.
|
|
"""
|
|
timeout = 10
|
|
start_time = time.time()
|
|
sleep_for = 0.01
|
|
while True:
|
|
if os.path.exists(self.env_file):
|
|
with open(self.env_file, 'r') as file:
|
|
for line in file.readlines():
|
|
key, value = line.strip().split('=', 1)
|
|
os.environ[key] = value
|
|
break
|
|
|
|
if time.time() - start_time > timeout:
|
|
raise TimeoutError(f"Timeout waiting for file {self.env_file}")
|
|
# Sleep needed to wait when the controller will create a file with environment variables.
|
|
# Without sleep checking of the file existence will be too fast,
|
|
# so it will finish before the file is created
|
|
time.sleep(sleep_for)
|
|
sleep_for *=sleep_for
|
|
|
|
def cleanup(self) -> None:
|
|
"""
|
|
Fake method to have the same interfaces with Controller class.
|
|
"""
|
|
pass
|
|
|
|
def __enter__(self):
|
|
self.prepare()
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
self.cleanup()
|
|
|
|
|
|
class PrepareMainProcessEnv:
|
|
"""
|
|
A class responsible for starting additional services needed by tests.
|
|
|
|
It starts up a Minio server and an S3 mock server.
|
|
The environment settings are saved to a file for later consumption by child processes.
|
|
Class ensures that the necessary subdirectories exist or clean it if it exists
|
|
"""
|
|
def __init__(self, temp_dir: Path, modes: list[str], env_file: Path):
|
|
self.temp_dir = temp_dir
|
|
pytest_dirs = [self.temp_dir / mode / 'pytest' for mode in modes]
|
|
for directory in [self.temp_dir, *pytest_dirs]:
|
|
if not directory.exists():
|
|
os.makedirs(directory, exist_ok=True)
|
|
else:
|
|
shutil.rmtree(directory)
|
|
self.env_file = env_file
|
|
hosts = HostRegistry()
|
|
self.loop = asyncio.new_event_loop()
|
|
address_minio = self.loop.run_until_complete(hosts.lease_host())
|
|
address_s3_mock = self.loop.run_until_complete(hosts.lease_host())
|
|
self.address_s3_proxy = self.loop.run_until_complete(hosts.lease_host())
|
|
self.minio = MinioServer(self.temp_dir, address_minio, LogPrefixAdapter(logging.getLogger('minio'), {'prefix': 'minio'}))
|
|
self.mock_s3 = MockS3Server(address_s3_mock, 2012,
|
|
LogPrefixAdapter(logging.getLogger('s3_mock'), {'prefix': 's3_mock'}))
|
|
# S3 proxy initialized later because it needs to know Minis address and port that will be available only after
|
|
# Minio will start
|
|
self.proxy_s3 = None
|
|
|
|
def prepare(self) -> None:
|
|
"""
|
|
Start the S3 mock server and MinIO for the tests.
|
|
Create a file with environment variables for connecting to them.
|
|
"""
|
|
|
|
tasks = [
|
|
self.loop.create_task(self.minio.start()),
|
|
self.loop.create_task(self.mock_s3.start()),
|
|
]
|
|
self.loop.run_until_complete(asyncio.gather(*tasks))
|
|
envs = self.minio.get_envs_settings()
|
|
envs.update(self.mock_s3.get_envs_settings())
|
|
minio_uri = "http://" + envs[self.minio.ENV_ADDRESS] + ":" + envs[self.minio.ENV_PORT]
|
|
self.proxy_s3 = S3ProxyServer(self.address_s3_proxy, 9002, minio_uri, 3, int(time.time()),
|
|
LogPrefixAdapter(logging.getLogger('s3_proxy'), {'prefix': 's3_proxy'}))
|
|
self.loop.run_until_complete(self.proxy_s3.start())
|
|
envs.update(self.proxy_s3.get_envs_settings())
|
|
with open(self.env_file, 'w') as file:
|
|
for key, value in envs.items():
|
|
file.write(f"{key}={value}\n")
|
|
|
|
def cleanup(self) -> None:
|
|
"""
|
|
Stop the S3 mock server and MinIO
|
|
Remove the file with environment variables to not mess for consecutive runs.
|
|
"""
|
|
tasks = [
|
|
self.loop.create_task(self.minio.stop()),
|
|
self.loop.create_task(self.mock_s3.stop()),
|
|
self.loop.create_task(self.proxy_s3.stop()),
|
|
]
|
|
self.loop.run_until_complete(asyncio.gather(*tasks))
|
|
if os.path.exists(self.env_file):
|
|
self.env_file.unlink()
|
|
|
|
def __enter__(self):
|
|
try:
|
|
self.prepare()
|
|
except Exception:
|
|
self.cleanup()
|
|
raise
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
self.cleanup()
|
|
|
|
@contextmanager
|
|
def get_env_manager(temp_dir: Path, is_worker: bool, modes: list[str]) -> Generator[None, Any, None]:
|
|
"""
|
|
xdist helps to execute test in parallel.
|
|
For that purpose it creates one main controller and workers.
|
|
Pytest itself doesn't know if it's a worker or controller, so it will execute all fixtures and methods.
|
|
Tests need S3 mock server and minio to start only once for the whole run, since they can share the one instance and
|
|
share the environment variables with workers.
|
|
|
|
So the part of starting the servers executes on non-workers' processes.
|
|
That means when xdist isn't used, servers start as intended in the main process.
|
|
Tests on workers should know the endpoints of the servers, so the controller prepares this information.
|
|
According classes responsible for configuration controller and workers.
|
|
"""
|
|
env_file = Path(f"{temp_dir}/services_envs").absolute()
|
|
if is_worker:
|
|
with PrepareChildProcessEnv(env_file):
|
|
yield
|
|
else:
|
|
with PrepareMainProcessEnv(temp_dir, modes, env_file):
|
|
yield
|