diff --git a/backend_server/database.py b/backend_server/database.py index afcb72ca..83bb134a 100644 --- a/backend_server/database.py +++ b/backend_server/database.py @@ -109,6 +109,10 @@ def test_case_log_messages(self, test_run_id, test_case): sql = sql_queries.log_messages(test_run_id, test_id=test_case) return self.session.query(sql), list_of_dicts + def test_case_log_messages_with_build(self, series, build_number, test_case): + sql = sql_queries.log_messages_with_build(series, build_number, test_id=test_case) + return self.session.query(sql), list_of_dicts + def keyword_tree(self, fingerprint): sql = "SELECT * FROM keyword_tree WHERE fingerprint=%(fingerprint)s" return self.session.query(sql, {'fingerprint': fingerprint}), single_dict @@ -119,6 +123,11 @@ def subtrees(self, fingerprint): def keyword_analysis(self, test_series, build_number): return self.session.query(sql_queries.keyword_analysis(test_series, build_number)), list_of_dicts + def keyword_tree_with_test_id(self, series, build_number, test_id): + return self.session.query(sql_queries.fingerprints_with_id(series, build_number, test_id)), list_of_dicts + + def keyword_tree_with_test_run_and_id(self, test_run_id, test_id): + return self.session.query(sql_queries.fingerprints_with_run_and_id(test_run_id, test_id)), list_of_dicts def list_of_dicts(rows): results = [] diff --git a/backend_server/server.py b/backend_server/server.py index d4cc0113..2c817897 100644 --- a/backend_server/server.py +++ b/backend_server/server.py @@ -1,7 +1,7 @@ import argparse import json import sys - +import copy import tornado.httpserver import tornado.ioloop import tornado.web @@ -336,7 +336,15 @@ def __init__(self, database): SuiteLogMessageDataHandler), url(r"/data/test_runs/(?P[0-9]+)/test_cases/(?P[0-9]+)/log_messages?$", TestCaseLogMessageDataHandler), - url(r"/data/keyword_tree/(?P[0-9a-fA-F]{40})/?$", KeywordTreeDataHandler), + url( + r"/data/keyword_tree/(?P[0-9a-fA-F]{40})/?$", KeywordTreeDataHandler), + url( + r"/data/testcase_keywords/series/(?P[0-9]+)/builds/(?P[0-9]+)/test_id/(?P[0-9]+)", TestcaseKeywordHandler), + url( + r"/data/testcase_keywords/test_run_id/(?P[0-9]+)/test_id/(?P[0-9]+)", TestcaseKeywordHandlerWithRun), + + # url(r"/data/history/?$", OldHistoryDataHandler), # Depricated see HistoryDataHandler + # url(r"/data/metadata/?$", OldMetaDataHandler), # Depricated see MetaDataHandler # For query testing purposes only url(r"/data/foo/?$", FooDataHandler) @@ -423,6 +431,7 @@ def keyword_tree(self, fingerprint): if keyword_tree: keyword_tree['children'] = [] keyword_tree = yield self.child_trees(keyword_tree) + keyword_tree['log_messages'] = [] return keyword_tree return None @@ -435,13 +444,14 @@ def child_trees(self, keyword_tree): child_tree = self.tree_from_cache(child['fingerprint']) if not child_tree: child_tree = yield self.child_trees(child) + child_tree['log_messages'] = [] self._keyword_tree_cache[child['fingerprint']] = child_tree keyword_tree['children'].append(child_tree) return keyword_tree def tree_from_cache(self, fingerprint): try: - return self._keyword_tree_cache.get(fingerprint, None) + return copy.deepcopy(self._keyword_tree_cache.get(fingerprint, None)) except AttributeError: self._keyword_tree_cache = {} return None @@ -1521,13 +1531,156 @@ def get(self, series, build_number): statistics = yield coroutine_query(self.database.keyword_analysis, series, build_number) self.write({'statistics': statistics}) +class KeywordLogHandler(BaseHandler): + def generate_keyword_array(self, testcase_fingerprints, log_messages): + keyword_array = {'setup': None, 'execution': None, 'teardown': None} + + setup_fingerprint=(testcase_fingerprints[0]['setup_fingerprint']) + execution_fingerprint=(testcase_fingerprints[0]['execution_fingerprint']) + teardown_fingerprint=(testcase_fingerprints[0]['teardown_fingerprint']) + if setup_fingerprint: + keyword_array['setup'] = yield self.keyword_tree(setup_fingerprint.lower()) + if execution_fingerprint: + keyword_array['execution'] = yield self.keyword_tree(execution_fingerprint.lower()) + if teardown_fingerprint: + keyword_array['teardown'] = yield self.keyword_tree(teardown_fingerprint.lower()) + + amount_of_setup= 1 if not keyword_array['setup'] == None else 0 + amount_of_execution=int(len(keyword_array['execution']['children'])) + + for log in log_messages: + parsed_execution_path = (self.parse_execution_path(log['execution_path'])) + if(int(parsed_execution_path[0]) <= amount_of_setup): + if(len(parsed_execution_path) == 1): + keyword_array['setup']['log_messages'].append(log['message']) + else: + self.set_log(keyword_array['setup'], parsed_execution_path, log, 'setup') + elif(int(parsed_execution_path[0]) <= (amount_of_setup + amount_of_execution)): + if amount_of_setup == 1: + self.set_log(keyword_array['execution'], parsed_execution_path, log, 'execution') + else: + self.set_log(keyword_array['execution'], parsed_execution_path, log, 'execution_no_setup') + else: + if(len(parsed_execution_path) == 1): + keyword_array['teardown']['log_messages'].append(log['message']) + else: + self.set_log(keyword_array['teardown'], parsed_execution_path, log, 'teardown') + + return keyword_array + + @classmethod + def set_log(self, tree, path, log, state): + if state == 'setup' or state =='teardown': + path=path[1:] + else: + # If the state is only execution we assume there is a setup and fix indexes accordingly + if(state == 'execution'): + path[0]=(path[0]-1) + for number in path: + if(number-1 < 0): + tree=tree['children'][number] + else: + tree=tree['children'][(number-1)] + + tree['log_messages'].append(log['message']) + + @classmethod + def parse_execution_path(self, execution_path): + remove_test_case = execution_path.split('-k') + test_cases_int = list(map(lambda x: int(x), remove_test_case[1:])) + return test_cases_int + +class TestcaseKeywordHandler(KeywordLogHandler): + @tornado.gen.coroutine + def get(self, series, build_number, test_id): + """ + --- + tags: + - Test Case Keywords + summary: Keywords of Test Cases + description: List of keywords within a Test Case Setup, Execution, Teardown and their logs, in the order of execution + produces: + - application/json + parameters: + - name: series + in: path + description: series id + required: true + type: integer + - name: build_number + in: path + description: build number + required: true + type: integer + - name: test_id + in: path + description: test id + required: true + type: integer + responses: + 200: + description: List of keywords within a Test Case Setup, Execution, Teardown and their logs + schema: + type: object + properties: + statistics: + type: array + items: + $ref: '#/definitions/BuildKeywordAnalysisObjectModel' + """ + try: + testcase_fingerprints = yield coroutine_query(self.database.keyword_tree_with_test_id, series, build_number, test_id) + log_messages = yield coroutine_query(self.database.test_case_log_messages_with_build, series, build_number, test_id) + keyword_array = yield from self.generate_keyword_array(testcase_fingerprints, log_messages) + self.write({'keywords': keyword_array}) + except IndexError: + self.send_not_found_response() +class TestcaseKeywordHandlerWithRun(KeywordLogHandler): + @tornado.gen.coroutine + def get(self, test_run_id, test_id): + """ + --- + tags: + - Test Case Keywords + summary: Keywords of Test Cases With Test Run ID + description: List of keywords within a Test Case Setup, Execution, Teardown and their logs, in the order of execution + produces: + - application/json + parameters: + - name: test_run_id + in: path + description: test run id + required: true + type: integer + - name: test_id + in: path + description: test id + required: true + type: integer + responses: + 200: + description: List of keywords within a Test Case Setup, Execution, Teardown and their logs + schema: + type: object + properties: + statistics: + type: array + items: + $ref: '#/definitions/BuildKeywordAnalysisObjectModel' + """ + try: + testcase_fingerprints = yield coroutine_query(self.database.keyword_tree_with_test_run_and_id, test_run_id, test_id) + log_messages = yield coroutine_query(self.database.test_case_log_messages, test_run_id, test_id) + keyword_array = yield from self.generate_keyword_array(testcase_fingerprints, log_messages) + self.write({'keywords': keyword_array}) + except IndexError: + self.send_not_found_response() + class FooDataHandler(BaseHandler): def get(self): self.write({'suites': []}) - - def main(): parser = argparse.ArgumentParser( description='Test manager 2.0 backend server') diff --git a/backend_server/sql_queries.py b/backend_server/sql_queries.py index 255e7ff3..39d4369a 100644 --- a/backend_server/sql_queries.py +++ b/backend_server/sql_queries.py @@ -474,6 +474,20 @@ def log_messages(test_run_id, suite_id=None, test_id=None): suite_filter="AND suite_id={}".format(int(suite_id)) if suite_id else '', test_filter="test_id={}".format(int(test_id)) if test_id else 'test_id IS NULL') +def log_messages_with_build(series, build_number, suite_id=None, test_id=None): + return """ +SELECT * +FROM log_message +WHERE test_run_id IN ({test_run_ids}) + AND {test_filter} + {suite_filter} +ORDER BY timestamp, id +""".format(suite_filter="AND suite_id={}".format(int(suite_id)) if suite_id else '', + test_filter="test_id={}".format(int(test_id)) if test_id else 'test_id IS NULL', + test_run_ids=test_run_ids(series, build_num=build_number)) + + + def most_stable_tests(series, start_from, last, offset, limit, limit_offset, stable): return """ SELECT suite_id, suite_name, suite_full_name, @@ -544,3 +558,23 @@ def keyword_analysis(series, build_number): GROUP BY tree.library, tree.keyword, total_elapsed.total ORDER BY total DESC """.format(test_run_ids=test_run_ids(series, build_num=build_number)) + +def fingerprints_with_run_and_id(test_run_id, test_id): + return """ +SELECT setup_fingerprint, execution_fingerprint, + teardown_fingerprint, test_id, + test_run_id, execution_path +FROM test_result +WHERE test_id={test_id} and test_run_id={test_run_id} +""".format(test_id=test_id, + test_run_id= test_run_id) + +def fingerprints_with_id(series, build_number,test_id): + return """ +SELECT setup_fingerprint, execution_fingerprint, + teardown_fingerprint, test_id, + test_run_id, execution_path +FROM test_result +WHERE test_id={test_id} and test_run_id IN ({test_run_ids}) +""".format(test_id=test_id, + test_run_ids=test_run_ids(series, build_num=build_number)) \ No newline at end of file diff --git a/end_to_end_tests/robot_tests/backend/api/testing_log_exec_path.robot b/end_to_end_tests/robot_tests/backend/api/testing_log_exec_path.robot new file mode 100644 index 00000000..a37814b3 --- /dev/null +++ b/end_to_end_tests/robot_tests/backend/api/testing_log_exec_path.robot @@ -0,0 +1,25 @@ +*** Settings *** +Force Tags exec_path + +*** Test cases *** + +API documentation page + [Setup] Run Keywords Empty Keyword Info Log Keyword + [Teardown] Run Keywords Empty Keyword Info Log Keyword + Empty Keyword + Info Log Keyword + Warn Log Keyword + Empty Keyword + Info Log Keyword + +*** Keywords *** + + +Empty Keyword + No Operation + +Info Log Keyword + Log Keyword INFO + +Warn Log Keyword + Log Keyword WARN \ No newline at end of file