Some test suites have own test runners based on pytest, and they don't need all stuff we use for test.py. Move all code related to test.py framework to test/pylib/runner.py and use it as a plugin conditionally (by using TEST_RUNNER variable.)
302 lines
14 KiB
Python
302 lines
14 KiB
Python
# Copyright 2020-present ScyllaDB
|
|
#
|
|
# SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
|
|
|
# This file configures pytest for all tests in this directory, and also
|
|
# defines common test fixtures for all of them to use. A "fixture" is some
|
|
# setup which an individual test requires to run; The fixture has setup code
|
|
# and teardown code, and if multiple tests require the same fixture, it can
|
|
# be set up only once - while still allowing the user to run individual tests
|
|
# and automatically setting up the fixtures they need.
|
|
|
|
import pytest
|
|
import logging
|
|
import pathlib
|
|
import boto3
|
|
from cassandra.cluster import Cluster, NoHostAvailable
|
|
from cassandra.connection import DRIVER_NAME, DRIVER_VERSION
|
|
import json
|
|
import os
|
|
import ssl
|
|
import subprocess
|
|
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
|
|
|
|
print(f"Driver name {DRIVER_NAME}, version {DRIVER_VERSION}")
|
|
|
|
|
|
# By default, tests run against a CQL server (Scylla or Cassandra) listening
|
|
# on localhost:9042. Add the --host and --port options to allow overriding
|
|
# these defaults.
|
|
def pytest_addoption(parser):
|
|
add_host_option(parser)
|
|
add_cql_connection_options(parser)
|
|
parser.addoption('--no-minio', action="store_true", help="Signal to not run S3 related tests")
|
|
add_s3_options(parser)
|
|
|
|
|
|
@pytest.fixture(scope=testpy_test_fixture_scope)
|
|
async def host(request, testpy_test: PythonTest | None):
|
|
if testpy_test is None:
|
|
yield request.config.getoption("--host")
|
|
else:
|
|
async with testpy_test.run_ctx(options=testpy_test.suite.options):
|
|
yield testpy_test.server_address
|
|
|
|
|
|
# "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)
|
|
def cql(request, host):
|
|
port = request.config.getoption("--port")
|
|
try:
|
|
# Use the default superuser credentials, which work for both Scylla and Cassandra
|
|
with cql_session(
|
|
host=host,
|
|
port=port,
|
|
is_ssl=request.config.getoption("--ssl"),
|
|
username=request.config.getoption("--auth_username") or "cassandra",
|
|
password=request.config.getoption("--auth_password") or "cassandra",
|
|
) as session:
|
|
yield session
|
|
session.shutdown()
|
|
except NoHostAvailable:
|
|
# We couldn't create a cql connection. Instead of reporting that
|
|
# each individual test failed, let's just exit immediately.
|
|
pytest.exit(f"Cannot connect to Scylla at --host={host} --port={port}", returncode=pytest.ExitCode.INTERNAL_ERROR)
|
|
|
|
# A function-scoped autouse=True fixture allows us to test after every test
|
|
# that the CQL connection is still alive - and if not report the test which
|
|
# crashed Scylla and stop running any more tests.
|
|
@pytest.fixture(scope="function", autouse=True)
|
|
def cql_test_connection(cql, request):
|
|
scylla_log(cql, f'test/cqlpy: Starting {request.node.parent.name}::{request.node.name}', 'info')
|
|
if cql_test_connection.scylla_crashed:
|
|
pytest.skip('Server down')
|
|
yield
|
|
try:
|
|
# We want to run a do-nothing CQL command.
|
|
# "BEGIN BATCH APPLY BATCH" is the closest to do-nothing I could find...
|
|
cql.execute("BEGIN BATCH APPLY BATCH")
|
|
except:
|
|
cql_test_connection.scylla_crashed = True
|
|
pytest.fail(f'Scylla appears to have crashed in test {request.node.parent.name}::{request.node.name}')
|
|
scylla_log(cql, f'test/cqlpy: Ended {request.node.parent.name}::{request.node.name}', 'info')
|
|
|
|
cql_test_connection.scylla_crashed = False
|
|
|
|
# Until Cassandra 4, NetworkTopologyStrategy did not support the option
|
|
# replication_factor (https://issues.apache.org/jira/browse/CASSANDRA-14303).
|
|
# We want to allow these tests to run on Cassandra 3.* (for the convenience
|
|
# of developers who happen to have it installed), so we'll use the older
|
|
# 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)
|
|
def this_dc(cql):
|
|
yield cql.execute("SELECT data_center FROM system.local").one()[0]
|
|
|
|
@pytest.fixture(scope=testpy_test_fixture_scope)
|
|
def test_keyspace_tablets(cql, this_dc, has_tablets):
|
|
if not is_scylla(cql) or not has_tablets:
|
|
yield None
|
|
return
|
|
|
|
name = unique_name()
|
|
cql.execute("CREATE KEYSPACE " + name + " WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 } AND TABLETS = {'enabled': true}")
|
|
yield name
|
|
cql.execute("DROP KEYSPACE " + name)
|
|
|
|
@pytest.fixture(scope=testpy_test_fixture_scope)
|
|
def test_keyspace_vnodes(cql, this_dc, has_tablets):
|
|
name = unique_name()
|
|
if has_tablets:
|
|
cql.execute("CREATE KEYSPACE " + name + " WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 } AND TABLETS = {'enabled': false}")
|
|
else:
|
|
# If tablets are not available or not enabled, we just create a regular keyspace
|
|
cql.execute("CREATE KEYSPACE " + name + " WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 }")
|
|
yield name
|
|
cql.execute("DROP KEYSPACE " + name)
|
|
|
|
# "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)
|
|
def test_keyspace(request, test_keyspace_vnodes, test_keyspace_tablets, cql, this_dc):
|
|
if hasattr(request, "param"):
|
|
if request.param == "vnodes":
|
|
yield test_keyspace_vnodes
|
|
elif request.param == "tablets":
|
|
if not test_keyspace_tablets:
|
|
pytest.skip("tablet-specific test skipped")
|
|
yield test_keyspace_tablets
|
|
else:
|
|
pytest.fail(f"test_keyspace(): invalid request parameter: {request.param}")
|
|
else:
|
|
name = unique_name()
|
|
cql.execute("CREATE KEYSPACE " + name + " WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" + this_dc + "' : 1 }")
|
|
yield name
|
|
cql.execute("DROP KEYSPACE " + name)
|
|
|
|
# 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)
|
|
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')
|
|
|
|
# "cassandra_bug" is similar to "scylla_only", except instead of skipping
|
|
# 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)
|
|
def cassandra_bug(cql):
|
|
# We recognize Scylla by checking if there is any system table whose name
|
|
# contains the word "scylla":
|
|
names = [row.table_name for row in cql.execute("SELECT * FROM system_schema.tables WHERE keyspace_name = 'system'")]
|
|
if not any('scylla' in name for name in names):
|
|
pytest.xfail('A known Cassandra bug')
|
|
|
|
# Consistent schema change feature is optionally enabled and
|
|
# some tests are expected to fail on Scylla without this
|
|
# option enabled, and pass with it enabled (and also pass on Cassandra).
|
|
# These tests should use the "fails_without_consistent_cluster_management"
|
|
# fixture. When consistent mode becomes the default, this fixture can be removed.
|
|
@pytest.fixture(scope="function")
|
|
def check_pre_consistent_cluster_management(cql):
|
|
# If not running on Scylla, return false.
|
|
names = [row.table_name for row in cql.execute("SELECT * FROM system_schema.tables WHERE keyspace_name = 'system'")]
|
|
if not any('scylla' in name for name in names):
|
|
return False
|
|
# In Scylla, we check Raft mode by inspecting the configuration via CQL.
|
|
consistent = list(cql.execute("SELECT value FROM system.config WHERE name = 'consistent_cluster_management'"))
|
|
return len(consistent) == 0 or consistent[0].value == "false"
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def fails_without_consistent_cluster_management(request, check_pre_consistent_cluster_management):
|
|
if check_pre_consistent_cluster_management:
|
|
request.node.add_marker(pytest.mark.xfail(reason="Test expected to fail without consistent cluster management "
|
|
"feature on"))
|
|
# Older versions of the Cassandra driver had a bug where if Scylla returns
|
|
# an empty page, the driver would immediately stop reading even if this was
|
|
# not the last page. Some tests which filter out most of the results can end
|
|
# up with some empty pages, and break on buggy versions of the driver. These
|
|
# tests should be skipped when using a buggy version of the driver. This is
|
|
# the purpose of the following fixture.
|
|
# This driver bug was fixed in Scylla driver 3.24.5 and Datastax driver
|
|
# 3.25.1, in the following commits:
|
|
# https://github.com/scylladb/python-driver/commit/6ed53d9f7004177e18d9f2ea000a7d159ff9278e,
|
|
# https://github.com/datastax/python-driver/commit/1d9077d3f4c937929acc14f45c7693e76dde39a9
|
|
@pytest.fixture(scope="function")
|
|
def driver_bug_1():
|
|
scylla_driver = 'Scylla' in DRIVER_NAME
|
|
driver_version = tuple(int(x) for x in DRIVER_VERSION.split('.'))
|
|
if (scylla_driver and driver_version < (3, 24, 5) or
|
|
not scylla_driver and driver_version <= (3, 25, 0)):
|
|
pytest.skip("Python driver too old to run this test")
|
|
|
|
# `random_seed` fixture should be used when the test uses random module.
|
|
# If the fixture is used, the seed is visible in case of test's failure,
|
|
# so it can be easily recreated.
|
|
# The state of random module is restored to before-test state after the test finishes.
|
|
@pytest.fixture(scope="function")
|
|
def random_seed():
|
|
state = random.getstate()
|
|
seed = time.time()
|
|
print(f"Using seed {seed}")
|
|
random.seed(seed)
|
|
yield seed
|
|
random.setstate(state)
|
|
|
|
# TODO: use new_test_table and "yield from" to make shared test_table
|
|
# fixtures with some common schemas.
|
|
|
|
# To run the Scylla tools, we need to run Scylla executable itself, so we
|
|
# need to find the path of the executable that was used to run Scylla for
|
|
# this test. We do this by trying to find a local process which is listening
|
|
# to the address and port to which our our CQL connection is connected.
|
|
# 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)
|
|
def scylla_path(cql):
|
|
pid = local_process_id(cql)
|
|
if not pid:
|
|
pytest.skip("Can't find local Scylla process")
|
|
# Now that we know the process id, use /proc to find the executable.
|
|
try:
|
|
path = os.readlink(f'/proc/{pid}/exe')
|
|
except:
|
|
pytest.skip("Can't find local Scylla executable")
|
|
# Confirm that this executable is a real tool-providing Scylla by trying
|
|
# to run it with the "--list-tools" option
|
|
try:
|
|
subprocess.check_output([path, '--list-tools'])
|
|
except:
|
|
pytest.skip("Local server isn't Scylla")
|
|
return path
|
|
|
|
# A fixture for finding Scylla's data directory. We get it using the CQL
|
|
# interface to Scylla's configuration. Note that if the server is remote,
|
|
# the directory retrieved this way may be irrelevant, whether or not it
|
|
# 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")
|
|
def scylla_data_dir(cql):
|
|
try:
|
|
dir = json.loads(cql.execute("SELECT value FROM system.config WHERE name = 'data_file_directories'").one().value)[0]
|
|
return dir
|
|
except:
|
|
pytest.skip("Can't find Scylla sstable directory")
|
|
|
|
@pytest.fixture(scope="function")
|
|
def temp_workdir():
|
|
""" Creates a temporary work directory, for the scope of a single test. """
|
|
with tempfile.TemporaryDirectory() as workdir:
|
|
yield workdir
|
|
|
|
@pytest.fixture(scope=testpy_test_fixture_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)
|
|
|
|
@pytest.fixture(scope="function")
|
|
def skip_without_tablets(scylla_only, has_tablets):
|
|
if not has_tablets:
|
|
pytest.skip("Test needs tablets experimental feature on")
|
|
|
|
# Recent versions of Scylla deprecated the "WITH COMPACT STORAGE" feature,
|
|
# but it can be enabled temporarily for a test. So to keep our old compact
|
|
# storage tests alive for a while longer (at least until this feature is
|
|
# completely removed from Scylla), the "compact_storage" fixture can be
|
|
# added to enable WITH COMPACT STORAGE for the duration of this test.
|
|
@pytest.fixture(scope="function")
|
|
def compact_storage(cql):
|
|
try:
|
|
with config_value_context(cql, 'enable_create_table_with_compact_storage', 'true') as ctx:
|
|
yield ctx
|
|
except:
|
|
# enable_create_table_with_compact_storage is a scylla only feature
|
|
# so the above may fail on cassandra.
|
|
# This is fine since compact storage is enabled there by default.
|
|
yield
|
|
|
|
# Skip tests that require a running Minio server if the --no-minio option is set, intended to be set from test/cqlpy/run
|
|
# Otherwise, use the provided minio server to run all S3 related tests
|
|
@pytest.fixture
|
|
def skip_s3_tests(request):
|
|
if request.config.getoption("--no-minio"):
|
|
pytest.skip("Skipping S3 related tests being run from test/cqlpy/run")
|