import re from threading import Timer from .event_dispatcher import TEST_COMPLETED_EVENT from ..data.exceptions.not_found_exception import NotFoundException from ..data.session import COMPLETED, ABORTED # Helper class used to adapt a specific compare function to a Python 3 # key function. class CmpWrapper: def __init__(self, test_manager, compare_function, obj): self.obj = obj self.test_manager = test_manager self.compare_function = compare_function def __lt__(self, other): return self.compare_function(self.test_manager, self.obj, other.obj) < 0 def __gt__(self, other): return self.compare_function(self.test_manager, self.obj, other.obj) > 0 def __eq__(self, other): return self.compare_function(self.test_manager, self.obj, other.obj) == 0 def __le__(self, other): return self.compare_function(self.test_manager, self.obj, other.obj) <= 0 def __ge__(self, other): return self.compare_function(self.test_manager, self.obj, other.obj) >= 0 def __ne__(self, other): return self.compare_function(self.test_manager, self.obj, other.obj) != 0 class TestsManager(object): def initialize( self, test_loader, sessions_manager, results_manager, event_dispatcher ): self._test_loader = test_loader self._sessions_manager = sessions_manager self._results_manager = results_manager self._event_dispatcher = event_dispatcher self._timeouts = [] def next_test(self, session): if session.status == COMPLETED or session.status == ABORTED: return None pending_tests = session.pending_tests running_tests = session.running_tests token = session.token if pending_tests is None: pending_tests = self.load_tests(session) session.pending_tests = pending_tests self._sessions_manager.update_session(session) if running_tests is None: running_tests = {} test = self._get_next_test_from_list(pending_tests) if test is None: return None pending_tests = self.remove_test_from_list(pending_tests, test) running_tests = self.add_test_to_list(running_tests, test) test_timeout = self.get_test_timeout(test, session) / 1000.0 def handler(self, token, test): self._on_test_timeout(token, test) timer = Timer(test_timeout, handler, [self, token, test]) self._timeouts.append({ "test": test, "timeout": timer }) session.pending_tests = pending_tests session.running_tests = running_tests self._sessions_manager.update_session(session) timer.start() return test def read_last_completed_tests(self, token, count): results = self._results_manager.read_results(token) results_tests = {} for api in list(results.keys()): results_tests[api] = [] for result in results[api]: results_tests[api].append(result["test"]) sorted_results_tests = self._sort_tests_by_execution(results_tests) sorted_results_tests.reverse() tests = {"pass": [], "fail": [], "timeout": []} for test in sorted_results_tests: api = None for part in test.split("/"): if part != "": api = part break result = None for potential_result in results[api]: if potential_result["test"] == test: result = potential_result break if result["status"] == "ERROR": if len(tests["fail"]) < count: tests["fail"].append(result["test"]) elif result["status"] == "TIMEOUT": if len(tests["timeout"]) < count: tests["timeout"].append(result["test"]) passes = True for test in result["subtests"]: if test["status"] != "PASS": passes = False break if passes and len(tests["pass"]) < count: tests["pass"].append(result["test"]) if not passes and len(tests["fail"]) < count: tests["fail"].append(result["test"]) if len(tests["pass"]) == count and len(tests["fail"]) == count \ and len(tests["timeout"]) == count: return tests return tests def _sort_tests_by_execution(self, tests): sorted_tests = [] for api in list(tests.keys()): for test in tests[api]: sorted_tests.append(test) def compare(tests_manager, test_a, test_b): micro_test_list = {} api_a = "" for part in test_a.split("/"): if part != "": api_a = part break api_b = "" for part in test_b.split("/"): if part != "": api_b = part break if api_a == api_b: micro_test_list[api_a] = [test_a, test_b] else: micro_test_list[api_a] = [test_a] micro_test_list[api_b] = [test_b] next_test = tests_manager._get_next_test_from_list(micro_test_list) if next_test == test_a: return -1 return 1 return sorted(sorted_tests, key=lambda x:CmpWrapper(self, compare, x)) def _get_next_test_from_list(self, tests): test = None api = None has_http = True has_manual = True current_api = 0 current_test = 0 apis = list(tests.keys()) apis.sort(key=lambda api: api.lower()) for api in apis: tests[api].sort(key=lambda api: api.replace("/", "").lower()) while test is None: if len(apis) <= current_api: return None api = apis[current_api] if len(tests[api]) <= current_test: current_api = current_api + 1 current_test = 0 if current_api == len(apis): if has_http: has_http = False current_api = 0 test = None continue if has_manual: has_manual = False current_api = 0 test = None has_http = True continue return None test = None continue test = tests[api][current_test] if "manual" in test and "https" not in test: return test if "manual" in test and "https" in test: if not has_http: return test if "manual" not in test and "https" not in test: if not has_manual: return test if "manual" not in test and "https" in test: if not has_manual and not has_http: return test current_test = current_test + 1 test = None return test def skip_to(self, test_list, test): sorted_tests = self._sort_tests_by_execution(test_list) if test not in sorted_tests: return test_list index = sorted_tests.index(test) remaining_tests = sorted_tests[index + 1:] remaining_tests_by_api = {} current_api = "___" for test in remaining_tests: if not test.startswith("/" + current_api) and \ not test.startswith(current_api): current_api = next((p for p in test.split("/") if p != ""), None) if current_api not in remaining_tests_by_api: remaining_tests_by_api[current_api] = [] remaining_tests_by_api[current_api].append(test) return remaining_tests_by_api def remove_test_from_list(self, test_list, test): api = None for part in test.split("/"): if part is None or part == "": continue api = part break if api not in test_list: return test_list if test not in test_list[api]: return test_list test_list[api].remove(test) if len(test_list[api]) == 0: del test_list[api] return test_list def add_test_to_list(self, test_list, test): api = None for part in test.split("/"): if part is None or part == "": continue api = part break if api in test_list and test in test_list[api]: return test_list if api not in test_list: test_list[api] = [] test_list[api].append(test) return test_list def get_test_timeout(self, test, session): timeouts = session.timeouts test_timeout = None for path in list(timeouts.keys()): pattern = re.compile("^" + path.replace(".", "")) if pattern.match(test.replace(".", "")) is not None: test_timeout = timeouts[path] break if test_timeout is None: if "manual" in test: test_timeout = timeouts["manual"] else: test_timeout = timeouts["automatic"] return test_timeout def _on_test_timeout(self, token, test): data = { "test": test, "status": "TIMEOUT", "message": None, "subtests": [ { "status": "TIMEOUT", "xstatus": "SERVERTIMEOUT" } ] } self._results_manager.create_result(token, data) def read_tests(self): return self._test_loader.get_tests() def complete_test(self, test, session): running_tests = session.running_tests running_tests = self.remove_test_from_list(running_tests, test) session.running_tests = running_tests timeout = next((t for t in self._timeouts if t["test"] == test), None) timeout["timeout"].cancel() self._timeouts.remove(timeout) self.update_tests( running_tests=running_tests, session=session ) self._event_dispatcher.dispatch_event( token=session.token, event_type=TEST_COMPLETED_EVENT, data=test ) def update_tests( self, pending_tests=None, running_tests=None, session=None ): if pending_tests is not None: session.pending_tests = pending_tests if running_tests is not None: session.running_tests = running_tests self._sessions_manager.update_session(session) def calculate_test_files_count(self, tests): count = {} for api in tests: count[api] = len(tests[api]) return count def read_malfunctioning_tests(self, token): session = self._sessions_manager.read_session(token) return session.malfunctioning_tests def update_malfunctioning_tests(self, token, tests): if token is None: return if tests is None: return session = self._sessions_manager.read_session(token) if session is None: raise NotFoundException("Could not find session using token: " + token) if session.is_public: return session.malfunctioning_tests = tests self._sessions_manager.update_session(session) def load_tests(self, session): pending_tests = self._test_loader.get_tests( session.types, include_list=session.tests["include"], exclude_list=session.tests["exclude"], reference_tokens=session.reference_tokens ) last_completed_test = session.last_completed_test if last_completed_test is not None: pending_tests = self.skip_to(pending_tests, last_completed_test) return pending_tests