| 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 |