test.py: remove testpy_test_fixture_scope

With migration to pyest this fixture is useless. Removing and setting
the session to the module for the most of the tests.
Add dynamic_scope function to support running alternator fixtures in
session scope, while Test and TestSuite are not deleted. This is for
migration period, later on this function should be deleted.
This commit is contained in:
Andrei Chekun
2026-03-11 13:53:48 +01:00
parent 21addb2173
commit 745debe9ec
9 changed files with 60 additions and 66 deletions

View File

@@ -15,9 +15,9 @@ import requests
import re
from test.alternator.util import create_test_table, is_aws, scylla_log
from test.conftest import dynamic_scope
from test.cqlpy.conftest import host # add required fixtures
from test.pylib.driver_utils import safe_driver_shutdown
from test.pylib.runner import testpy_test_fixture_scope
from test.pylib.suite.python import add_host_option
from urllib.parse import urlparse
from functools import cache
@@ -75,7 +75,7 @@ def pytest_collection_modifyitems(config, items):
# If this function can't connect to CQL, it will return an arbitrary
# user/secret pair, and hope it would work if alternator-enforce-authorization
# is off.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def get_valid_alternator_role():
from cassandra.cluster import Cluster, NoHostAvailable
from cassandra.auth import PlainTextAuthProvider
@@ -117,7 +117,7 @@ def get_valid_alternator_role():
# or a local Alternator installation on http://localhost:8080 - depending on the
# existence of the "--aws" option. In the future we should provide options
# for choosing other Amazon regions or local installations.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def dynamodb(request, get_valid_alternator_role):
# Disable boto3's client-side validation of parameters. This validation
# only makes it impossible for us to test various error conditions,
@@ -146,7 +146,7 @@ def dynamodb(request, get_valid_alternator_role):
yield res
res.meta.client.close()
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def new_dynamodb_session(request, dynamodb, get_valid_alternator_role):
def _new_dynamodb_session(user='cassandra', password='secret_pass'):
ses = boto3.Session()
@@ -163,7 +163,7 @@ def new_dynamodb_session(request, dynamodb, get_valid_alternator_role):
config=conf)
return _new_dynamodb_session
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def dynamodbstreams(request, get_valid_alternator_role):
# Disable boto3's client-side validation of parameters. This validation
# only makes it impossible for us to test various error conditions,
@@ -235,7 +235,7 @@ dynamodb_test_connection.scylla_crashed = False
# time, we can also remove just tables older than a particular age. Such
# mechanism will allow running tests in parallel, without the risk of deleting
# a parallel run's temporary tables.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_table(dynamodb):
table = create_test_table(dynamodb,
KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' },
@@ -254,7 +254,7 @@ def test_table(dynamodb):
# The following fixtures test_table_* are similar to test_table but create
# tables with different key schemas.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_table_s(dynamodb):
table = create_test_table(dynamodb,
KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' }, ],
@@ -263,35 +263,35 @@ def test_table_s(dynamodb):
table.delete()
# test_table_s_2 has exactly the same schema as test_table_s, and is useful
# for tests which need two different tables with the same schema.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_table_s_2(dynamodb):
table = create_test_table(dynamodb,
KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' }, ],
AttributeDefinitions=[ { 'AttributeName': 'p', 'AttributeType': 'S' } ])
yield table
table.delete()
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_table_b(dynamodb):
table = create_test_table(dynamodb,
KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' }, ],
AttributeDefinitions=[ { 'AttributeName': 'p', 'AttributeType': 'B' } ])
yield table
table.delete()
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_table_sb(dynamodb):
table = create_test_table(dynamodb,
KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' }, { 'AttributeName': 'c', 'KeyType': 'RANGE' } ],
AttributeDefinitions=[ { 'AttributeName': 'p', 'AttributeType': 'S' }, { 'AttributeName': 'c', 'AttributeType': 'B' } ])
yield table
table.delete()
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_table_sn(dynamodb):
table = create_test_table(dynamodb,
KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' }, { 'AttributeName': 'c', 'KeyType': 'RANGE' } ],
AttributeDefinitions=[ { 'AttributeName': 'p', 'AttributeType': 'S' }, { 'AttributeName': 'c', 'AttributeType': 'N' } ])
yield table
table.delete()
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_table_ss(dynamodb):
table = create_test_table(dynamodb,
KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' }, { 'AttributeName': 'c', 'KeyType': 'RANGE' } ],
@@ -308,7 +308,7 @@ def test_table_ss(dynamodb):
# This table is supposed to be read from, not updated nor overwritten.
# This fixture returns both a table object and the description of all items
# inserted into it.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def filled_test_table(dynamodb):
table = create_test_table(dynamodb,
KeySchema=[ { 'AttributeName': 'p', 'KeyType': 'HASH' },
@@ -343,7 +343,7 @@ def filled_test_table(dynamodb):
# The "scylla_only" fixture can be used by tests for Scylla-only features,
# which do not exist on AWS DynamoDB. A test using this fixture will be
# skipped if running with "--aws".
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def scylla_only(dynamodb):
if is_aws(dynamodb):
pytest.skip('Scylla-only feature not supported by AWS')
@@ -354,7 +354,7 @@ def scylla_only(dynamodb):
# corect one, and DynamoDB's to be the bug. Tests using this fixture should
# have a prominent comment explaining why we believe this to be a bug in
# DynamoDB.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def dynamodb_bug(dynamodb):
if is_aws(dynamodb):
pytest.xfail('A known bug in AWS DynamoDB')
@@ -363,12 +363,12 @@ def dynamodb_bug(dynamodb):
# If we're not testing Scylla, or the REST API port (10000) is not available,
# the test using this fixture will be skipped with a message about the REST
# API not being available.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def rest_api(dynamodb, optional_rest_api):
if optional_rest_api is None:
pytest.skip('Cannot connect to Scylla REST API')
return optional_rest_api
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def optional_rest_api(dynamodb):
if is_aws(dynamodb):
return None
@@ -389,7 +389,7 @@ def optional_rest_api(dynamodb):
# below to xfail or skip a test which is known to be failing with tablets.
# This is a temporary measure - eventually everything in Scylla should work
# correctly with tablets, and these fixtures can be removed.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def has_tablets(dynamodb, test_table):
# We rely on some knowledge of Alternator internals:
# 1. For table with name X, Scylla creates a keyspace called alternator_X
@@ -427,7 +427,7 @@ def skip_tablets(has_tablets):
# If we're not testing Scylla, or the CQL port is not available on the same
# IP address as the Alternator IP address, a test using this fixture will
# be skipped with a message about the CQL API not being available.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def cql(dynamodb):
from cassandra.auth import PlainTextAuthProvider
from cassandra.cluster import Cluster, ConsistencyLevel, ExecutionProfile, EXEC_PROFILE_DEFAULT, NoHostAvailable

View File

@@ -19,7 +19,6 @@ from multiprocessing import Event
from pathlib import Path
from typing import TYPE_CHECKING
from test import TOP_SRC_DIR, path_to
from test.pylib.runner import testpy_test_fixture_scope
from test.pylib.random_tables import RandomTables
from test.pylib.util import unique_name
from test.pylib.manager_client import ManagerClient
@@ -182,7 +181,7 @@ def cluster_con(hosts: list[IPAddress | EndPoint], port: int = 9042, use_ssl: bo
)
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope="module")
async def manager_api_sock_path(request: pytest.FixtureRequest, testpy_test: Test | None) -> AsyncGenerator[str]:
if testpy_test is None:
yield request.config.getoption("--manager-api")
@@ -213,7 +212,7 @@ async def manager_api_sock_path(request: pytest.FixtureRequest, testpy_test: Tes
future.result()
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope="module")
async def manager_internal(request: pytest.FixtureRequest, manager_api_sock_path: str) -> Callable[[], ManagerClient]:
"""Session fixture to prepare client object for communicating with the Cluster API.
Pass the Unix socket path where the Manager server API is listening.

View File

@@ -4,6 +4,7 @@
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.1
#
import _pytest
import allure
import pytest
from test import TEST_RUNNER
@@ -14,6 +15,17 @@ from test.pylib.skip_types import SkipType
pytest_plugins = []
def dynamic_scope() -> _pytest.scope._ScopeName:
"""Dynamic scope for fixtures which rely on a current test.py suite/test.
Even though test.py not running tests anymore, there is some logic still there that requires module scope.
When using runpy runner, all custom logic should be disabled and scope should be session.
"""
if TEST_RUNNER == "runpy":
return "session"
return "module"
if TEST_RUNNER == "runpy":
@pytest.fixture(scope="session")
def testpy_test() -> None:

View File

@@ -13,7 +13,6 @@ import pytest
from test.cqlpy.conftest import host, cql, this_dc # add required fixtures
from test.pylib.cql_repl import CQL_TEST_SUFFIX, CqlFile
from test.pylib.runner import testpy_test_fixture_scope
from test.pylib.suite.base import get_testpy_test
from test.pylib.suite.python import add_host_option, add_cql_connection_options
@@ -61,7 +60,7 @@ def cql_test_connection(cql: Session) -> Generator[None]:
pytest.fail(f"Scylla appears to have crashed: {exc}")
@pytest.fixture(scope=testpy_test_fixture_scope, autouse=True)
@pytest.fixture(scope="module", autouse=True)
def keyspace(cql: Session, this_dc: str) -> Generator[str]:
"""Create a random keyspace for this pytest session.

View File

@@ -19,10 +19,10 @@ import tempfile
import time
import random
from test.pylib.runner import testpy_test_fixture_scope
from test.pylib.suite.python import PythonTest, add_host_option, add_cql_connection_options, add_s3_options
from .util import unique_name, new_test_keyspace, keyspace_has_tablets, cql_session, local_process_id, is_scylla, config_value_context
from .nodetool import scylla_log
from ..conftest import dynamic_scope
print(f"Driver name {DRIVER_NAME}, version {DRIVER_VERSION}")
@@ -37,7 +37,7 @@ def pytest_addoption(parser):
add_s3_options(parser)
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
async def host(request, testpy_test: PythonTest | None):
if testpy_test is None:
yield request.config.getoption("--host")
@@ -49,7 +49,7 @@ async def host(request, testpy_test: PythonTest | None):
# "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
# --port options, and defaults to localhost and 9042, respectively.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def cql(request, host):
port = request.config.getoption("--port")
try:
@@ -95,11 +95,11 @@ cql_test_connection.scylla_crashed = False
# syntax that needs to specify a DC name explicitly. For this, will have
# a "this_dc" fixture to figure out the name of the current DC, so it can be
# used in NetworkTopologyStrategy.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def this_dc(cql):
yield cql.execute("SELECT data_center FROM system.local").one()[0]
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_keyspace_tablets(cql, this_dc, has_tablets):
if not is_scylla(cql) or not has_tablets:
yield None
@@ -110,7 +110,7 @@ def test_keyspace_tablets(cql, this_dc, has_tablets):
yield name
cql.execute("DROP KEYSPACE " + name)
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_keyspace_vnodes(cql, this_dc, has_tablets):
name = unique_name()
if has_tablets:
@@ -124,7 +124,7 @@ def test_keyspace_vnodes(cql, this_dc, has_tablets):
# "test_keyspace" fixture: Creates and returns a temporary keyspace to be
# used in tests that need a keyspace. The keyspace is created with RF=1,
# and automatically deleted at the end.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_keyspace(request, test_keyspace_vnodes, test_keyspace_tablets, cql, this_dc):
if hasattr(request, "param"):
if request.param == "vnodes":
@@ -144,7 +144,7 @@ def test_keyspace(request, test_keyspace_vnodes, test_keyspace_tablets, cql, thi
# The "scylla_only" fixture can be used by tests for Scylla-only features,
# which do not exist on Apache Cassandra. A test using this fixture will be
# skipped if running with "run-cassandra".
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def scylla_only(cql):
# We recognize Scylla by checking if there is any system table whose name
# contains the word "scylla":
@@ -155,7 +155,7 @@ def scylla_only(cql):
# the test, it is expected to fail (xfail) on Cassandra. It should be used
# in rare cases where we consider Scylla's behavior to be the correct one,
# and Cassandra's to be the bug.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def cassandra_bug(cql):
# We recognize Scylla by checking if there is any system table whose name
# contains the word "scylla":
@@ -204,7 +204,7 @@ def random_seed():
# If such a process exists, we verify that it is Scylla, and return the
# executable's path. If we can't find the Scylla executable we use
# pytest.skip() to skip tests relying on this executable.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def scylla_path(cql):
pid = local_process_id(cql)
if not pid:
@@ -228,7 +228,7 @@ def scylla_path(cql):
# exists on the local machine... However, if the same test that uses this
# fixture also uses the scylla_path fixture, the test will anyway be skipped
# if the running Scylla is not on the local machine local.
@pytest.fixture(scope="module")
@pytest.fixture(scope=dynamic_scope())
def scylla_data_dir(cql):
try:
dir = json.loads(cql.execute("SELECT value FROM system.config WHERE name = 'data_file_directories'").one().value)[0]
@@ -242,7 +242,7 @@ def temp_workdir():
with tempfile.TemporaryDirectory() as workdir:
yield workdir
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def has_tablets(cql, this_dc):
with new_test_keyspace(cql, " WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', '" + this_dc + "': 1}") as keyspace:
return keyspace_has_tablets(cql, keyspace)

View File

@@ -20,7 +20,6 @@ from test import TOP_SRC_DIR, path_to
from test.nodetool.rest_api_mock import set_expected_requests, expected_request, get_expected_requests, \
get_unexpected_requests, expected_requests_manager
from test.pylib.db.model import Test
from test.pylib.runner import testpy_test_fixture_scope
def pytest_addoption(parser):
@@ -41,7 +40,7 @@ class ServerAddress(NamedTuple):
port: int
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope="module")
async def server_address(request, testpy_test: None|Test):
# unshare(1) -rn drops us in a new network namespace in which the "lo" is
# not up yet, so let's set it up first.
@@ -73,7 +72,7 @@ async def server_address(request, testpy_test: None|Test):
await testpy_test.suite.hosts.release_host(ip)
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope="module")
def rest_api_mock_server(request, server_address):
server_process = subprocess.Popen([sys.executable,
os.path.join(os.path.dirname(__file__), "rest_api_mock.py"),
@@ -110,7 +109,7 @@ def rest_api_mock_server(request, server_address):
server_process.wait()
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope="module")
def jmx(request, rest_api_mock_server):
if request.config.getoption("nodetool") == "scylla":
yield
@@ -171,7 +170,7 @@ def jmx(request, rest_api_mock_server):
jmx_process.wait()
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope="module")
def nodetool_path(request, build_mode):
if request.config.getoption("nodetool") == "scylla":
return path_to(build_mode, "scylla")

View File

@@ -133,21 +133,7 @@ def print_scylla_log_filename(request: pytest.FixtureRequest) -> Generator[None]
logger.info("ScyllaDB log file: %s", scylla_log_filename)
def testpy_test_fixture_scope(fixture_name: str, config: pytest.Config) -> _pytest.scope._ScopeName:
"""Dynamic scope for fixtures which rely on a current test.py suite/test.
test.py runs tests file-by-file as separate pytest sessions, so, `session` scope is effectively close to be the
same as `module` (can be a difference in the order.) In case of running tests with bare pytest command, we
need to use `module` scope to maintain same behavior as test.py, since we run all tests in one pytest session.
"""
if getattr(config.option, "test_py_init", False):
return "module"
return "session"
testpy_test_fixture_scope.__test__ = False
@pytest.fixture(scope=testpy_test_fixture_scope, autouse=True)
@pytest.fixture(scope="module", autouse=True)
def build_mode(request: pytest.FixtureRequest) -> str:
params_stash = get_params_stash(node=request.node)
if params_stash is None:
@@ -155,7 +141,7 @@ def build_mode(request: pytest.FixtureRequest) -> str:
return params_stash[BUILD_MODE]
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope="module")
def scale_timeout(build_mode: str) -> Callable[[int | float], int | float]:
def scale_timeout_inner(timeout: int | float) -> int | float:
return scale_timeout_by_mode(build_mode, timeout)
@@ -163,7 +149,7 @@ def scale_timeout(build_mode: str) -> Callable[[int | float], int | float]:
return scale_timeout_inner
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope="module")
async def testpy_test(request: pytest.FixtureRequest, build_mode: str) -> Test | None:
"""Create an instance of Test class for the current test.py test."""

View File

@@ -12,9 +12,9 @@
import pytest
import requests
from test.conftest import dynamic_scope
from test.cqlpy.conftest import host, cql, this_dc # add required fixtures
from test.cqlpy.util import unique_name, new_test_keyspace, keyspace_has_tablets, is_scylla
from test.pylib.runner import testpy_test_fixture_scope
from test.pylib.suite.python import add_host_option, add_cql_connection_options
# By default, tests run against a Scylla server listening
@@ -50,7 +50,7 @@ class RestApiSession:
# "api" fixture: set up client object for communicating with Scylla API.
# The host/port combination of the server are determined by the --host and
# --port options, and defaults to localhost and 10000, respectively.
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def rest_api(request, host):
port = request.config.getoption('api_port')
return RestApiSession(host, port)
@@ -58,14 +58,14 @@ def rest_api(request, host):
# The "scylla_only" fixture can be used by tests for Scylla-only features,
# which do not exist on Apache Cassandra. A test using this fixture will be
# skipped if running with "run-cassandra".
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def scylla_only(cql):
# We recognize Scylla by checking if there is any system table whose name
# contains the word "scylla":
if not is_scylla(cql):
pytest.skip('Scylla-only test skipped')
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def has_tablets(cql):
with new_test_keyspace(cql, " WITH REPLICATION = {'class' : 'NetworkTopologyStrategy', 'replication_factor': 1}") as keyspace:
return keyspace_has_tablets(cql, keyspace)
@@ -80,7 +80,7 @@ def skip_without_tablets(scylla_only, has_tablets):
if not has_tablets:
pytest.skip("Test needs tablets experimental feature on")
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_keyspace_vnodes(cql, this_dc, has_tablets):
name = unique_name()
if has_tablets:
@@ -91,7 +91,7 @@ def test_keyspace_vnodes(cql, this_dc, has_tablets):
yield name
cql.execute("DROP KEYSPACE " + name)
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope=dynamic_scope())
def test_keyspace(cql, this_dc):
name = unique_name()
cql.execute("CREATE KEYSPACE " + name + " WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 }")

View File

@@ -8,11 +8,10 @@ import subprocess
import pytest
from test.pylib.runner import testpy_test_fixture_scope
from test.pylib.suite.python import PythonTest
@pytest.fixture(scope=testpy_test_fixture_scope)
@pytest.fixture(scope="module")
async def scylla_server(testpy_test: PythonTest | None):
"""Return a running Scylla server instance from the active test cluster."""
async with testpy_test.run_ctx(options=testpy_test.suite.options) as cluster: