diff --git a/test/alternator/conftest.py b/test/alternator/conftest.py index 620afa60ec..3e59235291 100644 --- a/test/alternator/conftest.py +++ b/test/alternator/conftest.py @@ -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 diff --git a/test/cluster/conftest.py b/test/cluster/conftest.py index 4d28cf8054..7c0efc5407 100644 --- a/test/cluster/conftest.py +++ b/test/cluster/conftest.py @@ -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. diff --git a/test/conftest.py b/test/conftest.py index ce99a43e63..a1ac19cd40 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -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: diff --git a/test/cql/conftest.py b/test/cql/conftest.py index 89e1692c69..2bad30c5ec 100644 --- a/test/cql/conftest.py +++ b/test/cql/conftest.py @@ -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. diff --git a/test/cqlpy/conftest.py b/test/cqlpy/conftest.py index 9832eb6cb8..72e3cced43 100644 --- a/test/cqlpy/conftest.py +++ b/test/cqlpy/conftest.py @@ -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) diff --git a/test/nodetool/conftest.py b/test/nodetool/conftest.py index 5ce01e3e86..9d5dd17ece 100644 --- a/test/nodetool/conftest.py +++ b/test/nodetool/conftest.py @@ -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") diff --git a/test/pylib/runner.py b/test/pylib/runner.py index 07c4579c2d..b6e7840a35 100644 --- a/test/pylib/runner.py +++ b/test/pylib/runner.py @@ -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.""" diff --git a/test/rest_api/conftest.py b/test/rest_api/conftest.py index 1e3d0a23b0..f757e69d69 100644 --- a/test/rest_api/conftest.py +++ b/test/rest_api/conftest.py @@ -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 }") diff --git a/test/scylla_gdb/conftest.py b/test/scylla_gdb/conftest.py index 1953c188ff..9a4fe40d47 100644 --- a/test/scylla_gdb/conftest.py +++ b/test/scylla_gdb/conftest.py @@ -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: