Skip to content

Commit 717a953

Browse files
committed
test.py: add topology suite
1 parent ea8d374 commit 717a953

File tree

4 files changed

+193
-0
lines changed

4 files changed

+193
-0
lines changed

test/topology/conftest.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright 2020-present ScyllaDB
2+
#
3+
# This file is part of Scylla.
4+
#
5+
# Scylla is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU Affero General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# Scylla is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Affero General Public License
16+
# along with Scylla. If not, see <http://www.gnu.org/licenses/>.
17+
18+
# This file configures pytest for all tests in this directory, and also
19+
# defines common test fixtures for all of them to use. A "fixture" is some
20+
# setup which an invididual test requires to run; The fixture has setup code
21+
# and teardown code, and if multiple tests require the same fixture, it can
22+
# be set up only once - while still allowing the user to run individual tests
23+
# and automatically setting up the fixtures they need.
24+
25+
from cassandra.auth import PlainTextAuthProvider
26+
from cassandra.cluster import Cluster, ConsistencyLevel, ExecutionProfile, EXEC_PROFILE_DEFAULT
27+
from cassandra.policies import RoundRobinPolicy
28+
import pathlib
29+
import pytest
30+
import ssl
31+
import sys
32+
33+
sys.path.append(str(pathlib.Path(__file__).resolve().parents[1]))
34+
from pylib.util import random_string, unique_name
35+
36+
37+
# By default, tests run against a CQL server (Scylla or Cassandra) listening
38+
# on localhost:9042. Add the --host and --port options to allow overiding
39+
# these defaults.
40+
def pytest_addoption(parser):
41+
parser.addoption('--host', action='store', default='localhost',
42+
help='CQL server host to connect to')
43+
parser.addoption('--port', action='store', default='9042',
44+
help='CQL server port to connect to')
45+
parser.addoption('--ssl', action='store_true',
46+
help='Connect to CQL via an encrypted TLSv1.2 connection')
47+
48+
49+
# "cql" fixture: set up client object for communicating with the CQL API.
50+
# The host/port combination of the server are determined by the --host and
51+
# --port options, and defaults to localhost and 9042, respectively.
52+
# We use scope="session" so that all tests will reuse the same client object.
53+
@pytest.fixture(scope="session")
54+
def cql(request):
55+
profile = ExecutionProfile(
56+
load_balancing_policy=RoundRobinPolicy(),
57+
consistency_level=ConsistencyLevel.LOCAL_QUORUM,
58+
serial_consistency_level=ConsistencyLevel.LOCAL_SERIAL,
59+
# The default timeout (in seconds) for execute() commands is 10, which
60+
# should have been more than enough, but in some extreme cases with a
61+
# very slow debug build running on a very busy machine and a very slow
62+
# request (e.g., a DROP KEYSPACE needing to drop multiple tables)
63+
# 10 seconds may not be enough, so let's increase it. See issue #7838.
64+
request_timeout=120)
65+
if request.config.getoption('ssl'):
66+
# Scylla does not support any earlier TLS protocol. If you try,
67+
# you will get mysterious EOF errors (see issue #6971) :-(
68+
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
69+
else:
70+
ssl_context = None
71+
cluster = Cluster(execution_profiles={EXEC_PROFILE_DEFAULT: profile},
72+
contact_points=[request.config.getoption('host')],
73+
port=request.config.getoption('port'),
74+
# TODO: make the protocol version an option, to allow testing with
75+
# different versions. If we drop this setting completely, it will
76+
# mean pick the latest version supported by the client and the server.
77+
protocol_version=4,
78+
# Use the default superuser credentials, which work for both Scylla and Cassandra
79+
auth_provider=PlainTextAuthProvider(username='cassandra', password='cassandra'),
80+
ssl_context=ssl_context,
81+
)
82+
return cluster.connect()
83+
84+
85+
# A function-scoped autouse=True fixture allows us to test after every test
86+
# that the CQL connection is still alive - and if not report the test which
87+
# crashed Scylla and stop running any more tests.
88+
@pytest.fixture(scope="function", autouse=True)
89+
def cql_test_connection(cql, request):
90+
yield
91+
try:
92+
# We want to run a do-nothing CQL command. "use system" is the
93+
# closest to do-nothing I could find...
94+
cql.execute("use system")
95+
except: # noqa: E722
96+
pytest.exit(f"Scylla appears to have crashed in test {request.node.parent.name}::{request.node.name}")
97+
98+
99+
# Until Cassandra 4, NetworkTopologyStrategy did not support the option
100+
# replication_factor (https://issues.apache.org/jira/browse/CASSANDRA-14303).
101+
# We want to allow these tests to run on Cassandra 3.* (for the convenience
102+
# of developers who happen to have it installed), so we'll use the older
103+
# syntax that needs to specify a DC name explicitly. For this, will have
104+
# a "this_dc" fixture to figure out the name of the current DC, so it can be
105+
# used in NetworkTopologyStrategy.
106+
@pytest.fixture(scope="session")
107+
def this_dc(cql):
108+
yield cql.execute("SELECT data_center FROM system.local").one()[0]
109+
110+
111+
# "test_keyspace" fixture: Creates and returns a temporary keyspace to be
112+
# used in tests that need a keyspace. The keyspace is created with RF=1,
113+
# and automatically deleted at the end. We use scope="session" so that all
114+
# tests will reuse the same keyspace.
115+
@pytest.fixture(scope="session")
116+
def test_keyspace(cql, this_dc):
117+
name = unique_name()
118+
cql.execute("CREATE KEYSPACE " + name + " WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', '" +
119+
this_dc + "' : 3 }")
120+
yield name
121+
cql.execute("DROP KEYSPACE " + name)
122+
123+
124+
# The "scylla_only" fixture can be used by tests for Scylla-only features,
125+
# which do not exist on Apache Cassandra. A test using this fixture will be
126+
# skipped if running with "run-cassandra".
127+
@pytest.fixture(scope="session")
128+
def scylla_only(cql):
129+
# We recognize Scylla by checking if there is any system table whose name
130+
# contains the word "scylla":
131+
names = [row.table_name for row in cql.execute("SELECT * FROM system_schema.tables WHERE keyspace_name = 'system'")]
132+
if not any('scylla' in name for name in names):
133+
pytest.skip('Scylla-only test skipped')
134+
135+
136+
# "cassandra_bug" is similar to "scylla_only", except instead of skipping
137+
# the test, it is expected to fail (xfail) on Cassandra. It should be used
138+
# in rare cases where we consider Scylla's behavior to be the correct one,
139+
# and Cassandra's to be the bug.
140+
@pytest.fixture(scope="session")
141+
def cassandra_bug(cql):
142+
# We recognize Scylla by checking if there is any system table whose name
143+
# contains the word "scylla":
144+
names = [row.table_name for row in cql.execute("SELECT * FROM system_schema.tables WHERE keyspace_name = 'system'")]
145+
if not any('scylla' in name for name in names):
146+
pytest.xfail('A known Cassandra bug')

test/topology/pytest.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Pytest configuration file. If we don't have one in this directory,
2+
# pytest will look for one in our ancestor directories, and may find
3+
# something irrelevant. So we should have one here, even if empty.
4+
[pytest]

test/topology/suite.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
type: Python
2+
pool_size: 4
3+
topology:
4+
class: Simple
5+
replication_factor: 3
6+

test/topology/test_null.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright 2020-present ScyllaDB
2+
#
3+
# This file is part of Scylla.
4+
#
5+
# Scylla is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU Affero General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# Scylla is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU Affero General Public License
16+
# along with Scylla. If not, see <http://www.gnu.org/licenses/>.
17+
18+
from cassandra.protocol import InvalidRequest
19+
from pylib.util import random_string, unique_name
20+
import pytest
21+
22+
@pytest.fixture(scope="module")
23+
def table1(cql, test_keyspace):
24+
table = test_keyspace + "." + unique_name()
25+
cql.execute(f"CREATE TABLE {table} (p text, c text, v text, primary key (p, c))")
26+
yield table
27+
cql.execute("DROP TABLE " + table)
28+
29+
30+
def test_delete_empty_string_key(cql, table1):
31+
s = random_string()
32+
# An empty-string clustering *is* allowed:
33+
cql.execute(f"DELETE FROM {table1} WHERE p='{s}' AND c=''")
34+
# But an empty-string partition key is *not* allowed, with a specific
35+
# error that a "Key may not be empty":
36+
with pytest.raises(InvalidRequest, match='Key may not be empty'):
37+
cql.execute(f"DELETE FROM {table1} WHERE p='' AND c='{s}'")

0 commit comments

Comments
 (0)