test.py: Create central conftest.

Central conftest allows to reduce code duplication and execute all tests
with one pytest command

Closes scylladb/scylladb#21454
This commit is contained in:
Andrei Chekun
2024-11-05 21:09:44 +01:00
committed by Nadav Har'El
parent 7014aec452
commit 8bf62a086f
34 changed files with 85 additions and 207 deletions

View File

@@ -14,7 +14,6 @@ import boto3
import requests
import re
from test.pylib.report_plugin import ReportPlugin
from test.alternator.util import create_test_table, is_aws, scylla_log
from urllib.parse import urlparse
from functools import cache
@@ -50,13 +49,8 @@ def pytest_addoption(parser):
help='Omit scylla\'s output from the test output')
parser.addoption('--host', action='store', default='localhost',
help='Scylla server host to connect to')
parser.addoption('--mode', action='store', default=None,
help='Scylla build mode. Tests can use it to adjust their behavior.')
parser.addoption('--run_id', action='store', default=None,
help='Run id for the test run')
def pytest_configure(config):
config.addinivalue_line("markers", "veryslow: mark test as very slow to run")
config.pluginmanager.register(ReportPlugin())
def pytest_collection_modifyitems(config, items):
if config.getoption("--runveryslow"):

View File

@@ -1,12 +0,0 @@
# Pytest configuration file. If we don't have one in this directory,
# pytest will look for one in our ancestor directories, and may find
# something irrelevant - so we should keep this file even if in the future
# we decide that we don't need to configure anything.
[pytest]
# Ignore warnings about HTTPS requests without certificate verification
# (see issue #15287). Pytest breaks urllib3.disable_warnings() in conftest.py,
# so we need to do this here.
filterwarnings =
ignore::urllib3.exceptions.InsecureRequestWarning

View File

@@ -5,5 +5,9 @@
#
# This file configures pytest for all tests in this directory, and also
# defines common test fixtures for all of them to use
from pytest import Config
from test.topology.conftest import *
def pytest_configure(config: Config):
config.option.auth_username = 'cassandra'
config.option.auth_password = 'cassandra'

View File

@@ -1,12 +0,0 @@
[pytest]
addopts = --auth_username=cassandra --auth_password=cassandra
asyncio_mode = auto
log_cli = true
log_format = %(asctime)s.%(msecs)03d %(levelname)s> %(message)s
log_date_format = %H:%M:%S
markers =
slow: tests that take more than 30 seconds to run
replication_factor: replication factor for RandomTables

View File

@@ -233,7 +233,7 @@ async def assert_connections_params(manager: ManagerClient, hosts, expect):
@pytest.mark.asyncio
@skip_mode('release', 'cql server testing REST API is not supported in release mode')
async def test_connections_parameters_auto_update(manager: ManagerClient, mode):
async def test_connections_parameters_auto_update(manager: ManagerClient, build_mode):
servers = await manager.servers_add(3)
cql = manager.get_cql()
hosts = await wait_for_cql_and_get_hosts(cql, servers, time.time() + 60)
@@ -247,15 +247,15 @@ async def test_connections_parameters_auto_update(manager: ManagerClient, mode):
await assert_connections_params(manager, hosts, {
"r1": {
"workload_type": "unspecified",
"timeout": default_timeout(mode),
"timeout": default_timeout(build_mode),
},
"r2": {
"workload_type": "unspecified",
"timeout": default_timeout(mode),
"timeout": default_timeout(build_mode),
},
"r3": {
"workload_type": "unspecified",
"timeout": default_timeout(mode),
"timeout": default_timeout(build_mode),
},
})

View File

@@ -1,6 +0,0 @@
# Pytest configuration file. If we don't have one in this directory,
# pytest will look for one in our ancestor directories, and may find
# something irrelevant. So we should have one here, even if empty.
[pytest]
log_format = %(asctime)s.%(msecs)03d %(levelname)s> %(message)s
log_date_format = %H:%M:%S

27
test/conftest.py Normal file
View File

@@ -0,0 +1,27 @@
import pytest
from test.pylib.report_plugin import ReportPlugin
ALL_MODES = {'debug': 'Debug',
'release': 'RelWithDebInfo',
'dev': 'Dev',
'sanitize': 'Sanitize',
'coverage': 'Coverage'}
def pytest_addoption(parser):
parser.addoption('--mode', choices=ALL_MODES.keys(), dest="mode",
help="Run only tests for given build mode(s)")
parser.addoption('--tmpdir', action='store', default='testlog', help='''Path to temporary test data and log files. The data is
further segregated per build mode. Default: ./testlog.''', )
parser.addoption('--run_id', action='store', default=None, help='Run id for the test run')
@pytest.fixture(scope="session")
def build_mode(request):
"""
This fixture returns current build mode.
"""
return request.config.getoption('mode')
def pytest_configure(config):
config.pluginmanager.register(ReportPlugin())

View File

@@ -23,7 +23,6 @@ import random
import sys
sys.path.insert(1, sys.path[0] + '/../..')
from test.pylib.report_plugin import ReportPlugin
from util import unique_name, new_test_keyspace, keyspace_has_tablets, cql_session, local_process_id, is_scylla
@@ -45,10 +44,6 @@ def pytest_addoption(parser):
# presence.
parser.addoption('--omit-scylla-output', action='store_true',
help='Omit scylla\'s output from the test output')
parser.addoption('--mode', action='store', default=None,
help='Scylla build mode. Tests can use it to adjust their behavior.')
parser.addoption('--run_id', action='store', default=None,
help='Run id for the test run')
# "cql" fixture: set up client object for communicating with the CQL API.
# The host/port combination of the server are determined by the --host and
@@ -269,7 +264,3 @@ def has_tablets(cql, this_dc):
def skip_without_tablets(scylla_only, has_tablets):
if not has_tablets:
pytest.skip("Test needs tablets experimental feature on")
def pytest_configure(config):
config.pluginmanager.register(ReportPlugin())

View File

@@ -1,4 +0,0 @@
# Pytest configuration file. If we don't have one in this directory,
# pytest will look for one in our ancestor directories, and may find
# something irrelevant. So we should have one here, even if empty.
[pytest]

View File

@@ -16,12 +16,9 @@ import requests.exceptions
from test.nodetool.rest_api_mock import set_expected_requests, expected_request, get_expected_requests, \
get_unexpected_requests, expected_requests_manager
from test.pylib.report_plugin import ReportPlugin
def pytest_addoption(parser):
parser.addoption('--mode', action='store', default='dev',
help='Scylla build mode to use (default: %(default)s)')
parser.addoption('--nodetool', action='store', choices=["scylla", "cassandra"], default="scylla",
help="Which nodetool implementation to run the tests against (default: %(default)s)")
parser.addoption('--nodetool-path', action='store', default=None,
@@ -32,8 +29,6 @@ def pytest_addoption(parser):
help="Path to the jmx binary, only used with --nodetool=cassandra")
parser.addoption('--run-within-unshare', action='store_true',
help="Setup the 'lo' network if launched with unshare(1)")
parser.addoption('--run_id', action='store', default=1,
help='Run id for the test run (default: %(default)d)')
class ServerAddress(NamedTuple):
@@ -173,10 +168,9 @@ def _path_to_scylla(mode):
@pytest.fixture(scope="session")
def nodetool_path(request):
def nodetool_path(request, build_mode):
if request.config.getoption("nodetool") == "scylla":
mode = request.config.getoption("mode")
return _path_to_scylla(mode)
return _path_to_scylla(build_mode)
path = request.config.getoption("nodetool_path")
if path is not None:
@@ -239,7 +233,3 @@ def nodetool(request, jmx, nodetool_path, rest_api_mock_server):
return res
return invoker
def pytest_configure(config):
config.pluginmanager.register(ReportPlugin())

View File

@@ -23,8 +23,6 @@ def pytest_addoption(parser):
parser.addoption('--manager-api', action='store', required=True,
help='Manager unix socket path')
parser.addoption('--tmpdir', action='store', type=str, dest='tmpdir',
help='Temporary directory where logs are stored')
parser.addoption("--artifacts_dir_url", action='store', type=str, default=None, dest="artifacts_dir_url",
help="Provide the URL to artifacts directory to generate the link to failed tests directory "
"with logs")

View File

@@ -1,4 +0,0 @@
[pytest]
asyncio_mode = auto
tmp_path_retention_count = 1
tmp_path_retention_policy = failed

View File

@@ -21,7 +21,6 @@ from cassandra.policies import RoundRobinPolicy # type: ignore # pylint: disa
from cassandra.connection import DRIVER_NAME # type: ignore # pylint: disable=no-name-in-module
from cassandra.connection import DRIVER_VERSION # type: ignore # pylint: disable=no-name-in-module
from test.pylib.report_plugin import ReportPlugin
logger = logging.getLogger(__name__)
logger.warning("Driver name %s", DRIVER_NAME)
@@ -43,10 +42,6 @@ def pytest_addoption(parser) -> None:
help='CQL server port to connect to')
parser.addoption('--ssl', action='store_true',
help='Connect to CQL via an encrypted TLSv1.2 connection')
parser.addoption('--mode', action='store', required=True,
help='Scylla build mode. Tests can use it to adjust their behavior.')
parser.addoption('--run_id', action='store', default=1,
help='Run id for the test run')
# "cql" fixture: set up client object for communicating with the CQL API.
@@ -135,7 +130,3 @@ def keyspace(cql, this_dc): # pylint: disable=redefined-outer-name
f"WITH REPLICATION = {{ 'class' : 'NetworkTopologyStrategy', '{this_dc}' : 1 }}")
yield keyspace_name
cql.execute(f"DROP KEYSPACE {keyspace_name}")
def pytest_configure(config):
config.pluginmanager.register(ReportPlugin())

View File

@@ -12,21 +12,21 @@ from allure_pytest.utils import get_pytest_report_status
class ReportPlugin:
config = None
mode = None
build_mode = None
run_id = None
# Pytest hook to modify test name to include mode and run_id
def pytest_configure(self, config):
self.build_mode = config.getoption('mode')
self.config = config
self.mode = config.getoption("mode")
self.run_id = config.getoption("run_id")
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(self):
outcome = yield
report = outcome.get_result()
if self.mode is not None or self.run_id is not None:
report.nodeid = f"{report.nodeid}.{self.mode}.{self.run_id}"
if self.build_mode is not None or self.run_id is not None:
report.nodeid = f"{report.nodeid}.{self.build_mode}.{self.run_id}"
status = get_pytest_report_status(report)
# skip attaching logs for passed tests
# attach_capture is a destination for "--allure-no-capture" option from allure-plugin
@@ -44,9 +44,7 @@ class ReportPlugin:
Add mode tag to be able to search by it.
Add parameters to make allure distinguish them and not put them to retries.
"""
run_id = request.config.getoption('run_id')
mode = request.config.getoption('mode')
request.node.name = f"{request.node.name}.{mode}.{run_id}"
allure.dynamic.tag(mode)
allure.dynamic.parameter('mode', mode)
allure.dynamic.parameter('run_id', run_id)
request.node.name = f"{request.node.name}.{self.build_mode}.{self.run_id}"
allure.dynamic.tag(self.build_mode)
allure.dynamic.parameter('mode', self.build_mode)
allure.dynamic.parameter('run_id', self.run_id)

View File

@@ -1,9 +0,0 @@
# Pytest configuration file. If we don't have one in this directory,
# pytest will look for one in our ancestor directories, and may find
# something irrelevant. So we should have one here, even if empty.
[pytest]
asyncio_mode = auto
log_cli = true
log_format = %(asctime)s.%(msecs)03d %(levelname)s> %(message)s
log_date_format = %H:%M:%S

20
test/pytest.ini Normal file
View File

@@ -0,0 +1,20 @@
[pytest]
asyncio_mode = auto
log_cli = true
log_format = %(asctime)s.%(msecs)03d %(levelname)s> %(message)s
log_date_format = %H:%M:%S
markers =
slow: tests that take more than 30 seconds to run
replication_factor: replication factor for RandomTables
without_scylla: run without attaching to a scylla process
norecursedirs = manual perf lib
# Ignore warnings about HTTPS requests without certificate verification
# (see issue #15287). Pytest breaks urllib3.disable_warnings() in conftest.py,
# so we need to do this here.
filterwarnings =
ignore::urllib3.exceptions.InsecureRequestWarning
tmp_path_retention_count = 1
tmp_path_retention_policy = failed

View File

@@ -1,4 +0,0 @@
# Pytest configuration file. If we don't have one in this directory,
# pytest will look for one in our ancestor directories, and may find
# something irrelevant. So we should have one here, even if empty.
[pytest]

View File

@@ -18,7 +18,6 @@ from cassandra.auth import PlainTextAuthProvider
from cassandra.cluster import Cluster, ConsistencyLevel, ExecutionProfile, EXEC_PROFILE_DEFAULT
from cassandra.policies import RoundRobinPolicy
from test.pylib.report_plugin import ReportPlugin
# Use the util.py library from ../cqlpy:
sys.path.insert(1, sys.path[0] + '/test/cqlpy')
@@ -36,10 +35,6 @@ def pytest_addoption(parser):
help='Connect to CQL via an encrypted TLSv1.2 connection')
parser.addoption('--api-port', action='store', default='10000',
help='server REST API port to connect to')
parser.addoption('--mode', action='store', required=True,
help='Scylla build mode. Tests can use it to adjust their behavior.')
parser.addoption('--run_id', action='store', default=1,
help='Run id for the test run')
class RestApiSession:
def __init__(self, host, port):
@@ -162,7 +157,3 @@ def test_keyspace(cql, this_dc):
cql.execute("CREATE KEYSPACE " + name + " WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 }")
yield name
cql.execute("DROP KEYSPACE " + name)
def pytest_configure(config):
config.pluginmanager.register(ReportPlugin())

View File

@@ -1,4 +0,0 @@
# Pytest configuration file. If we don't have one in this directory,
# pytest will look for one in our ancestor directories, and may find
# something irrelevant. So we should have one here, even if empty.
[pytest]

View File

View File

@@ -1,6 +0,0 @@
# Pytest configuration file. If we don't have one in this directory,
# pytest will look for one in our ancestor directories, and may find
# something irrelevant. So we should have one here, even if empty.
[pytest]
markers =
without_scylla: run without attaching to a scylla process

View File

@@ -11,7 +11,6 @@ import platform
from functools import partial
from typing import List, Optional, Dict
from test.pylib.random_tables import RandomTables
from test.pylib.report_plugin import ReportPlugin
from test.pylib.util import unique_name
from test.pylib.manager_client import ManagerClient, IPAddress
from test.pylib.async_cql import event_loop, run_async
@@ -41,8 +40,6 @@ print(f"Driver name {DRIVER_NAME}, version {DRIVER_VERSION}")
def pytest_addoption(parser):
parser.addoption('--manager-api', action='store', required=True,
help='Manager unix socket path')
parser.addoption('--mode', action='store', required=True,
help='Scylla build mode. Tests can use it to adjust their behavior.')
parser.addoption('--host', action='store', default='localhost',
help='CQL server host to connect to')
parser.addoption('--port', action='store', default='9042',
@@ -53,13 +50,9 @@ def pytest_addoption(parser):
help='username for authentication')
parser.addoption('--auth_password', action='store', default=None,
help='password for authentication')
parser.addoption('--run_id', action='store', default=1,
help='Run id for the test run')
parser.addoption('--artifacts_dir_url', action='store', type=str, default=None, dest='artifacts_dir_url',
help='Provide the URL to artifacts directory to generate the link to failed tests directory '
'with logs')
parser.addoption('--tmpdir', action='store', type=str, dest='tmpdir',
help='Temporary directory where logs are stored')
# This is a constant used in `pytest_runtest_makereport` below to store the full report for the test case
@@ -178,7 +171,7 @@ async def manager_internal(event_loop, request):
@pytest.fixture(scope="function")
async def manager(request, manager_internal, record_property, mode):
async def manager(request, manager_internal, record_property, build_mode):
"""Per test fixture to notify Manager client object when tests begin so it can
perform checks for cluster state.
"""
@@ -187,7 +180,7 @@ async def manager(request, manager_internal, record_property, mode):
tmp_dir = pathlib.Path(request.config.getoption('tmpdir'))
xml_path: pathlib.Path = pathlib.Path(request.config.getoption('xmlpath'))
suite_testpy_log = (tmp_dir /
mode /
build_mode /
f"{pathlib.Path(xml_path.stem).stem}.log"
)
test_log = suite_testpy_log.parent / f"{suite_testpy_log.stem}.{test_case_name}.log"
@@ -205,7 +198,7 @@ async def manager(request, manager_internal, record_property, mode):
# Save scylladb logs for failed tests in a separate directory and copy XML report to the same directory to have
# all related logs in one dir.
# Then add property to the XML report with the path to the directory, so it can be visible in Jenkins
failed_test_dir_path = tmp_dir / mode / "failed_test" / f"{test_case_name}"
failed_test_dir_path = tmp_dir / build_mode / "failed_test" / f"{test_case_name}"
failed_test_dir_path.mkdir(parents=True, exist_ok=True)
await manager_internal.gather_related_logs(
failed_test_dir_path,
@@ -251,10 +244,6 @@ async def random_tables(request, manager):
if not failed and not await manager.is_dirty():
tables.drop_all()
@pytest.fixture(scope="function")
def mode(request):
return request.config.getoption('mode')
skipped_funcs = {}
# Can be used to mark a test to be skipped for a specific mode=[release, dev, debug]
# The reason to skip a test should be specified, used as a comment only.
@@ -267,16 +256,12 @@ def skip_mode(mode: str, reason: str, platform_key: Optional[str]=None):
return wrap
@pytest.fixture(scope="function", autouse=True)
def skip_mode_fixture(request, mode):
for reason, platform_key in skipped_funcs.get((request.function, mode), []):
def skip_mode_fixture(request, build_mode):
for reason, platform_key in skipped_funcs.get((request.function, build_mode), []):
if platform_key is None or platform_key in platform.platform():
pytest.skip(f'{request.node.name} skipped, reason: {reason}')
def pytest_configure(config):
config.pluginmanager.register(ReportPlugin())
def pytest_collection_modifyitems(items, config):
run_id = config.getoption('run_id')
for item in items:

View File

@@ -1,10 +0,0 @@
[pytest]
asyncio_mode = auto
log_cli = true
log_format = %(asctime)s.%(msecs)03d %(levelname)s> %(message)s
log_date_format = %H:%M:%S
markers =
slow: tests that take more than 30 seconds to run
replication_factor: replication factor for RandomTables

View File

@@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
@pytest.mark.asyncio
async def test_change_two(manager, random_tables, mode):
async def test_change_two(manager, random_tables, build_mode):
"""Stop two nodes, change their IPs and start, check the cluster is
functional"""
servers = await manager.running_servers()
@@ -104,7 +104,7 @@ async def test_change_two(manager, random_tables, mode):
# We're checking the crash scenario here - the servers[0] crashes just after
# saving s1_new_ip but before removing s1_old_ip. After its restart we should
# see s1_new_ip.
if mode != 'release':
if build_mode != 'release':
await manager.api.enable_injection(servers[0].ip_addr, 'crash-before-prev-ip-removed', one_shot=True)
# There is a code in raft_ip_address_updater::on_endpoint_change which
# calls gossiper.force_remove_endpoint for an endpoint if it sees
@@ -117,7 +117,7 @@ async def test_change_two(manager, random_tables, mode):
await manager.api.enable_injection(servers[0].ip_addr, 'ip-change-raft-sync-delay', one_shot=False)
await manager.server_start(servers[1].server_id)
servers[1] = ServerInfo(servers[1].server_id, s1_new_ip, s1_new_ip, servers[1].datacenter, servers[1].rack)
if mode != 'release':
if build_mode != 'release':
s0_logs = await manager.server_open_log(servers[0].server_id)
await s0_logs.wait_for('crash-before-prev-ip-removed hit, killing the node')
await manager.server_stop(servers[0].server_id)

View File

@@ -1,10 +0,0 @@
[pytest]
asyncio_mode = auto
log_cli = true
log_format = %(asctime)s.%(msecs)03d %(levelname)s> %(message)s
log_date_format = %H:%M:%S
markers =
slow: tests that take more than 30 seconds to run
replication_factor: replication factor for RandomTables

View File

@@ -51,11 +51,11 @@ async def check_features_status(cql, features, enabled):
@pytest.mark.asyncio
async def test_features_suppress_works(manager: ManagerClient, mode) -> None:
async def test_features_suppress_works(manager: ManagerClient, build_mode) -> None:
""" `suppress_features` error injection allows to revoke support for
specified cluster features. It can be used to simulate upgrade process.
"""
if mode == "release":
if build_mode == "release":
return
features_to_suppress = ["PARALLELIZED_AGGREGATION", "UDA_NATIVE_PARALLELIZED_AGGREGATION"]

View File

@@ -15,8 +15,8 @@ logger = logging.getLogger(__name__)
@pytest.fixture
def raft_op_timeout(mode):
return 10000 if mode == 'debug' else 1000
def raft_op_timeout(build_mode):
return 10000 if build_mode == 'debug' else 1000
@pytest.mark.asyncio

View File

@@ -247,10 +247,10 @@ async def test_multidc_alter_tablets_rf(request: pytest.FixtureRequest, manager:
# Check that an existing cached read, will be cleaned up when the tablet it reads
# from is migrated away.
@pytest.mark.asyncio
async def test_saved_readers_tablet_migration(manager: ManagerClient, mode):
async def test_saved_readers_tablet_migration(manager: ManagerClient, build_mode):
cfg = {'enable_user_defined_functions': False, 'enable_tablets': True}
if mode != "release":
if build_mode != "release":
cfg['error_injections_at_startup'] = [{'name': 'querier-cache-ttl-seconds', 'value': 999999999}]
servers = await manager.servers_add(2, config=cfg)

View File

@@ -1,10 +0,0 @@
[pytest]
asyncio_mode = auto
log_cli = true
log_format = %(asctime)s.%(msecs)03d %(levelname)s> %(message)s
log_date_format = %H:%M:%S
markers =
slow: tests that take more than 30 seconds to run
repair: tests for repair

View File

@@ -22,9 +22,9 @@ from test.topology.conftest import cluster_con
@pytest.mark.asyncio
@log_run_time
async def test_topology_recovery_basic(request, mode: str, manager: ManagerClient):
async def test_topology_recovery_basic(request, build_mode: str, manager: ManagerClient):
# Increase ring delay to ensure nodes learn about CDC generations before they start operating.
ring_delay = 15000 if mode == 'debug' else 5000
ring_delay = 15000 if build_mode == 'debug' else 5000
normal_cfg = {'ring_delay_ms': ring_delay}
zero_token_cfg = {'ring_delay_ms': ring_delay, 'join_ring': False}

View File

@@ -20,12 +20,12 @@ from test.topology.util import log_run_time, wait_until_last_generation_is_in_us
@pytest.mark.asyncio
@log_run_time
async def test_topology_upgrade_basic(request, mode: str, manager: ManagerClient):
async def test_topology_upgrade_basic(request, build_mode: str, manager: ManagerClient):
# First, force the first node to start in legacy mode
cfg = {
'force_gossip_topology_changes': True,
'enable_tablets': False,
'ring_delay_ms': 15000 if mode == 'debug' else 5000,
'ring_delay_ms': 15000 if build_mode == 'debug' else 5000,
}
servers = [await manager.server_add(config=cfg)]

View File

@@ -6,9 +6,4 @@
# This file configures pytest for all tests in this directory, and also
# defines common test fixtures for all of them to use
import pytest
pytest_plugins = [
"test.topology.conftest",
]
from test.topology.conftest import *

View File

@@ -1,6 +0,0 @@
[pytest]
asyncio_mode = auto
log_cli = true
log_format = %(asctime)s.%(msecs)03d %(levelname)s> %(message)s
log_date_format = %H:%M:%S

View File

@@ -1,9 +0,0 @@
[pytest]
asyncio_mode = auto
log_cli = true
log_format = %(asctime)s.%(msecs)03d %(levelname)s> %(message)s
log_date_format = %H:%M:%S
markers =
slow: tests that take more than 30 seconds to run