Add explicit default values to pytest command line options to prevent issues when running tests with pytest's parallel execution where options are not present on upper conftest, so they're just not set at all.
277 lines
13 KiB
Python
277 lines
13 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
|
|
from cassandra.cluster import NoHostAvailable
|
|
from cassandra.connection import DRIVER_NAME, DRIVER_VERSION
|
|
import json
|
|
import os
|
|
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')
|
|
|
|
# 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", default=None):
|
|
pytest.skip("Skipping S3 related tests being run from test/cqlpy/run")
|