blob: 7491a4b5a73cce3cdc8bb39ed8bdbbdba7195622 [file] [log] [blame]
import re
from time import sleep
from tools.misc import list_to_hashed_dict
from cassandra import (InvalidRequest, ReadFailure, ReadTimeout, Unauthorized,
Unavailable, WriteFailure, WriteTimeout)
from cassandra.query import SimpleStatement
"""
The assertion methods in this file are used to structure, execute, and test different queries and scenarios.
Use these anytime you are trying to check the content of a table, the row count of a table, if a query should
raise an exception, etc. These methods handle error messaging well, and will help discovering and treating bugs.
An example:
Imagine some table, test:
id | name
1 | John Doe
2 | Jane Doe
We could assert the row count is 2 by using:
assert_row_count(session, 'test', 2)
After inserting [3, 'Alex Smith'], we can ensure the table is correct by:
assert_all(session, "SELECT * FROM test", [[1, 'John Doe'], [2, 'Jane Doe'], [3, 'Alex Smith']])
or we could check the insert was successful:
assert_one(session, "SELECT * FROM test WHERE id = 3", [3, 'Alex Smith'])
We could remove all rows in test, and assert this was sucessful with:
assert_none(session, "SELECT * FROM test")
Perhaps we want to assert invalid queries will throw an exception:
assert_invalid(session, "SELECT FROM test")
or, maybe after shutting down all the nodes, we want to assert an Unavailable exception is raised:
assert_unavailable(session.execute, "SELECT * FROM test")
OR
assert_exception(session, "SELECT * FROM test", expected=Unavailable)
"""
def _rows_to_list(rows):
new_list = [list(row) for row in rows]
return new_list
def _assert_exception(fun, *args, **kwargs):
matching = kwargs.pop('matching', None)
expected = kwargs['expected']
try:
if len(args) == 0:
fun(None)
else:
fun(*args)
except expected as e:
if matching is not None:
regex = re.compile(matching)
assert regex.match(repr(e)) is None
except Exception as e:
raise e
else:
assert False, "Expecting query to raise an exception, but nothing was raised."
def assert_exception(session, query, matching=None, expected=None):
if expected is None:
assert False, "Expected exception should not be None. Your test code is wrong, please set `expected`."
_assert_exception(session.execute, query, matching=matching, expected=expected)
def assert_unavailable(fun, *args):
"""
Attempt to execute a function, and assert Unavailable, WriteTimeout, WriteFailure,
ReadTimeout, or ReadFailure exception is raised.
@param fun Function to be executed
@param *args Arguments to be passed to the function
Examples:
assert_unavailable(session2.execute, "SELECT * FROM ttl_table;")
assert_unavailable(lambda c: logger.debug(c.execute(statement)), session)
"""
_assert_exception(fun, *args, expected=(Unavailable, WriteTimeout, WriteFailure, ReadTimeout, ReadFailure))
def assert_invalid(session, query, matching=None, expected=InvalidRequest):
"""
Attempt to issue a query and assert that the query is invalid.
@param session Session to use
@param query Invalid query to run
@param matching Optional error message string contained within expected exception
@param expected Exception expected to be raised by the invalid query
Examples:
assert_invalid(session, 'DROP USER nonexistent', "nonexistent doesn't exist")
"""
assert_exception(session, query, matching=matching, expected=expected)
def assert_unauthorized(session, query, message):
"""
Attempt to issue a query, and assert Unauthorized is raised.
@param session Session to use
@param query Unauthorized query to run
@param message Expected error message
Examples:
assert_unauthorized(session, "ALTER USER cassandra NOSUPERUSER",
"You aren't allowed to alter your own superuser status")
assert_unauthorized(cathy, "ALTER TABLE ks.cf ADD val int",
"User cathy has no ALTER permission on <table ks.cf> or any of its parents")
"""
assert_exception(session, query, matching=message, expected=Unauthorized)
def assert_one(session, query, expected, cl=None):
"""
Assert query returns one row.
@param session Session to use
@param query Query to run
@param expected Expected results from query
@param cl Optional Consistency Level setting. Default ONE
Examples:
assert_one(session, "LIST USERS", ['cassandra', True])
assert_one(session, query, [0, 0])
"""
simple_query = SimpleStatement(query, consistency_level=cl)
res = session.execute(simple_query)
list_res = _rows_to_list(res)
assert list_res == [expected], "Expected {} from {}, but got {}".format([expected], query, list_res)
def assert_none(session, query, cl=None):
"""
Assert query returns nothing
@param session Session to use
@param query Query to run
@param cl Optional Consistency Level setting. Default ONE
Examples:
assert_none(self.session1, "SELECT * FROM test where key=2;")
assert_none(cursor, "SELECT * FROM test WHERE k=2", cl=ConsistencyLevel.SERIAL)
"""
simple_query = SimpleStatement(query, consistency_level=cl)
res = session.execute(simple_query)
list_res = _rows_to_list(res)
assert list_res == [], "Expected nothing from {}, but got {}".format(query, list_res)
def assert_some(session, query, cl=None, execution_profile=None):
"""
Assert query returns something
@param session Session to use
@param query Query to run
@param cl Optional Consistency Level setting. Default ONE
Examples:
assert_some(self.session1, "SELECT * FROM test where key=2;")
assert_some(cursor, "SELECT * FROM test WHERE k=2", cl=ConsistencyLevel.SERIAL)
"""
res = _execute(session, query, cl=cl, execution_profile=execution_profile)
list_res = _rows_to_list(res)
assert list_res != [], "Expected something from {}, but got {}".format(query, list_res)
def assert_all(session, query, expected, cl=None, ignore_order=False, timeout=None):
"""
Assert query returns all expected items optionally in the correct order
@param session Session in use
@param query Query to run
@param expected Expected results from query
@param cl Optional Consistency Level setting. Default ONE
@param ignore_order Optional boolean flag determining whether response is ordered
@param timeout Optional query timeout, in seconds
Examples:
assert_all(session, "LIST USERS", [['aleksey', False], ['cassandra', True]])
assert_all(self.session1, "SELECT * FROM ttl_table;", [[1, 42, 1, 1]])
"""
simple_query = SimpleStatement(query, consistency_level=cl)
res = session.execute(simple_query) if timeout is None else session.execute(simple_query, timeout=timeout)
list_res = _rows_to_list(res)
if ignore_order:
expected = list_to_hashed_dict(expected)
list_res = list_to_hashed_dict(list_res)
assert list_res == expected, "Expected {} from {}, but got {}".format(expected, query, list_res)
def assert_almost_equal(*args, **kwargs):
"""
Assert variable number of arguments all fall within a margin of error.
@params *args variable number of numerical arguments to check
@params error Optional margin of error. Default 0.16
@params error_message Optional error message to print. Default ''
Examples:
assert_almost_equal(sizes[2], init_size)
assert_almost_equal(ttl_session1, ttl_session2[0][0], error=0.005)
"""
error = kwargs['error'] if 'error' in kwargs else 0.16
vmax = max(args)
vmin = min(args)
error_message = '' if 'error_message' not in kwargs else kwargs['error_message']
assert vmin > vmax * (1.0 - error) or vmin == vmax, \
"values not within {:.2f}% of the max: {} ({})".format(error * 100, args, error_message)
def assert_row_count(session, table_name, expected, where=None):
"""
Assert the number of rows in a table matches expected.
@param session Session to use
@param table_name Name of the table to query
@param expected Number of rows expected to be in table
@param where string to append to CQL select query as where clause
Examples:
assert_row_count(self.session1, 'ttl_table', 1)
"""
if where is not None:
query = "SELECT count(*) FROM {} WHERE {};".format(table_name, where)
else:
query = "SELECT count(*) FROM {};".format(table_name)
res = session.execute(query)
count = res[0][0]
assert count == expected, "Expected a row count of {} in table '{}', but got {}".format(
expected, table_name, count
)
def assert_crc_check_chance_equal(session, table, expected, ks="ks", view=False):
"""
Assert crc_check_chance equals expected for a given table or view
@param session Session to use
@param table Name of the table or view to check
@param expected Expected value to assert on that query result matches
@param ks Optional Name of the keyspace
@param view Optional Boolean flag indicating if the table is a view
Examples:
assert_crc_check_chance_equal(session, "compression_opts_table", 0.25)
assert_crc_check_chance_equal(session, "t_by_v", 0.5, view=True)
driver still doesn't support top-level crc_check_chance property,
so let's fetch directly from system_schema
"""
if view:
assert_one(session,
"SELECT crc_check_chance from system_schema.views WHERE keyspace_name = '{keyspace}' AND "
"view_name = '{table}';".format(keyspace=ks, table=table),
[expected])
else:
assert_one(session,
"SELECT crc_check_chance from system_schema.tables WHERE keyspace_name = '{keyspace}' AND "
"table_name = '{table}';".format(keyspace=ks, table=table),
[expected])
def assert_length_equal(object_with_length, expected_length):
"""
Assert an object has a specific length.
@param object_with_length The object whose length will be checked
@param expected_length The expected length of the object
Examples:
assert_length_equal(res, nb_counter)
"""
assert len(object_with_length) == expected_length, \
"Expected {} to have length {}, but instead is of length {}"\
.format(object_with_length, expected_length, len(object_with_length))
def assert_not_running(node):
"""
Assert that a given node is not running
@param node The node to check status
"""
attempts = 0
while node.is_running() and attempts < 10:
sleep(1)
attempts = attempts + 1
assert not node.is_running()
def assert_read_timeout_or_failure(session, query):
assert_exception(session, query, expected=(ReadTimeout, ReadFailure))
def assert_stderr_clean(err, acceptable_errors=None):
"""
Assert that stderr is empty or that it only contains harmless messages
@param err The stderr to clean
@param acceptable_errors A list that if used, the user chooses what
messages are to be acceptable in stderr.
"""
if acceptable_errors is None:
acceptable_errors = ["WARN.*JNA link failure.*unavailable.",
"objc.*Class JavaLaunchHelper.*?Which one is undefined.",
# Stress tool JMX connection failure, see CASSANDRA-12437
"Failed to connect over JMX; not collecting these stats",
"Picked up JAVA_TOOL_OPTIONS:.*"]
regex_str = r"^({}|\s*|\n)*$".format("|".join(acceptable_errors))
err_str = err.strip()
# empty string, as good as we can get for a clean stderr output!
if not err_str:
return
match = re.search(regex_str, err_str)
assert match, "Attempted to check that stderr was empty. Instead, stderr is {}, but the regex used to check " \
"stderr is {}".format(err_str, regex_str)
def assert_bootstrap_state(tester, node, expected_bootstrap_state, user=None, password=None):
"""
Assert that a node is on a given bootstrap state
@param tester The dtest.Tester object to fetch the exclusive connection to the node
@param node The node to check bootstrap state
@param expected_bootstrap_state Bootstrap state to expect
@param user To connect as for authenticated nodes
@param password for corresponding user
Examples:
assert_bootstrap_state(self, node3, 'COMPLETED')
"""
session = tester.patient_exclusive_cql_connection(node, user=user, password=password)
assert_one(session, "SELECT bootstrapped FROM system.local WHERE key='local'", [expected_bootstrap_state])
session.shutdown()
def assert_lists_equal_ignoring_order(list1, list2, sort_key=None):
"""
asserts that the contents of the two provided lists are equal
but ignoring the order that the items of the lists are actually in
:param list1: list to check if it's contents are equal to list2
:param list2: list to check if it's contents are equal to list1
:param sort_key: if the contents of the list are of type dict, the
key to use of each object to sort the overall object with
"""
normalized_list1 = []
for obj in list1:
normalized_list1.append(obj)
normalized_list2 = []
for obj in list2:
normalized_list2.append(obj)
if not sort_key:
sorted_list1 = sorted(normalized_list1, key=lambda elm: elm[0])
sorted_list2 = sorted(normalized_list2, key=lambda elm: elm[0])
else:
# first always sort by "id"
# that way we get a two factor sort which will increase the chance of ordering lists exactly the same
if not sort_key == 'id' and 'id' in list1[0].keys():
sorted_list1 = sorted(sorted(normalized_list1, key=lambda elm: elm["id"]), key=lambda elm: elm[sort_key])
sorted_list2 = sorted(sorted(normalized_list2, key=lambda elm: elm["id"]), key=lambda elm: elm[sort_key])
else:
if isinstance(list1[0]['id'], (int, float)):
sorted_list1 = sorted(normalized_list1, key=lambda elm: elm[sort_key])
sorted_list2 = sorted(normalized_list2, key=lambda elm: elm[sort_key])
else:
sorted_list1 = sorted(normalized_list1, key=lambda elm: str(elm[sort_key]))
sorted_list2 = sorted(normalized_list2, key=lambda elm: str(elm[sort_key]))
assert sorted_list1 == sorted_list2
def assert_lists_of_dicts_equal(list1, list2):
for adict, bdict in zip(list1, list2):
assert(len(adict) == len(bdict))
for key, value in adict.items():
assert key in bdict
assert bdict[key] == value