diff --git a/workspace_tools/test_api.py b/workspace_tools/test_api.py index 6d629d2592..962d0d616c 100644 --- a/workspace_tools/test_api.py +++ b/workspace_tools/test_api.py @@ -197,8 +197,21 @@ class SingleTestRunner(object): self.opts_jobs = _opts_jobs if _opts_jobs is not None else 1 self.opts_extend_test_timeout = _opts_extend_test_timeout + # File / screen logger initialization self.logger = CLITestLogger(file_name=self.opts_log_file_name) # Default test logger + + # database releated initializations self.db_logger = factory_db_logger(self.opts_db_url) + self.db_logger_build_id = None # Build ID (database index of build_id table) + # Let's connect to database to set up credentials and confirm database is ready + if self.db_logger: + self.db_logger.connect_url(self.opts_db_url) # Save db access info inside db_logger object + if self.db_logger.is_connected(): + # Get hostname and uname so we can use it as build description + # when creating new build_id in external database + (_hostname, _uname) = self.db_logger.get_hostname() + self.db_logger_build_id = self.db_logger.get_next_build_id(_hostname, desc=_uname) + self.db_logger.disconnect() def shuffle_random_func(self): return self.shuffle_random_seed @@ -251,6 +264,18 @@ class SingleTestRunner(object): test_map_keys = TEST_MAP.keys() if self.opts_shuffle_test_order: random.shuffle(test_map_keys, self.shuffle_random_func) + # Update database with shuffle seed f applicable + if self.db_logger: + self.db_logger.reconnect(); + if self.db_logger.is_connected(): + self.db_logger.update_build_id_info(self.db_logger_build_id, _shuffle_seed=self.shuffle_random_func()) + self.db_logger.disconnect(); + + if self.db_logger: + self.db_logger.reconnect(); + if self.db_logger.is_connected(): + self.db_logger.update_build_id_info(self.db_logger_build_id, _muts=self.muts, _test_spec=self.test_spec) + self.db_logger.disconnect(); for test_id in test_map_keys: test = TEST_MAP[test_id] @@ -334,9 +359,17 @@ class SingleTestRunner(object): # which the test gets interrupted test_spec = self.shape_test_request(target, path, test_id, test_duration) test_loops = self.get_test_loop_count(test_id) + # read MUTs, test specification and perform tests single_test_result = self.handle(test_spec, target, toolchain, test_loops=test_loops) if single_test_result is not None: test_summary.append(single_test_result) + + if self.db_logger: + self.db_logger.reconnect(); + if self.db_logger.is_connected(): + self.db_logger.update_build_id_info(self.db_logger_build_id, _status_fk=self.db_logger.BUILD_ID_STATUS_COMPLETED) + self.db_logger.disconnect(); + return test_summary, self.shuffle_random_seed def generate_test_summary_by_target(self, test_summary, shuffle_seed=None): @@ -586,6 +619,9 @@ class SingleTestRunner(object): if not disk.endswith('/') and not disk.endswith('\\'): disk += '/' + if self.db_logger: + self.db_logger.reconnect() + # Tests can be looped so test results must be stored for the same test test_all_result = [] for test_index in range(test_loops): @@ -621,10 +657,26 @@ class SingleTestRunner(object): # Store test result test_all_result.append(single_test_result) - elapsed_time = time() - start_host_exec_time print self.print_test_result(single_test_result, target_name, toolchain_name, test_id, test_description, elapsed_time, duration) + + # Update database entries for ongoing test + if self.db_logger and self.db_logger.is_connected(): + test_type = 'SingleTest' + self.db_logger.insert_test_entry(self.db_logger_build_id, + target_name, + toolchain_name, + test_type, + test_id, + single_test_result, + elapsed_time, + duration, + test_index) + + if self.db_logger: + self.db_logger.disconnect() + return (self.shape_global_test_loop_result(test_all_result), target_name, toolchain_name, test_id, test_description, round(elapsed_time, 2), duration, self.shape_test_loop_ok_result_count(test_all_result)) @@ -1159,7 +1211,7 @@ class CLITestLogger(TestLogger): return timestamp_str + log_line_str def log_line(self, LogType, log_line, timestamp=True, line_delim='\n'): - """ Logs line, if log file output was specified log line will be appended + """ Logs line, if log file output was specified log line will be appended at the end of log file """ log_entry = TestLogger.log_line(self, LogType, log_line) diff --git a/workspace_tools/test_db.py b/workspace_tools/test_db.py index db847c174c..5fbb99ed97 100644 --- a/workspace_tools/test_db.py +++ b/workspace_tools/test_db.py @@ -18,6 +18,7 @@ Author: Przemyslaw Wirkus """ import re +import json class BaseDBAccess(): @@ -26,6 +27,12 @@ class BaseDBAccess(): def __init__(self): self.db_object = None self.db_type = None + # Connection credentials + self.host = None + self.user = None + self.passwd = None + self.db = None + # Test Suite DB scheme (table names) self.TABLE_BUILD_ID = 'mtest_build_id' self.TABLE_BUILD_ID_STATUS = 'mtest_build_id_status' @@ -67,6 +74,9 @@ class BaseDBAccess(): """ Parsing SQL DB connection string. String should contain: - DB Name, user name, password, URL (DB host), name Function should return tuple with parsed (db_type, username, password, host, db_name) or None if error + + (db_type, username, password, host, db_name) = self.parse_db_connection_string(db_url) + E.g. connection string: 'mysql://username:password@127.0.0.1/db_name' """ result = None @@ -87,6 +97,18 @@ class BaseDBAccess(): """ pass + def connect_url(self, db_url): + """ Connects to database using db_url (database url parsing), + store host, username, password, db_name + """ + pass + + def reconnect(self): + """ Reconnects to DB and returns DB object using stored host name, + database name and credentials (user name and password) + """ + pass + def disconnect(self): """ Close DB connection """ @@ -124,7 +146,14 @@ class BaseDBAccess(): """ pass - def insert_test_entry(self, next_build_id, target, toolchain, test_type, test_id, test_result, test_time, test_timeout, test_loop, test_extra=''): + def update_build_id_info(self, build_id, **kw): + """ Update additional data inside build_id table + Examples: + db.update_build_is(build_id, _status_fk=self.BUILD_ID_STATUS_COMPLETED, _shuffle_seed=0.0123456789): + """ + pass + + def insert_test_entry(self, build_id, target, toolchain, test_type, test_id, test_result, test_time, test_timeout, test_loop, test_extra=''): """ Inserts test result entry to database. All checks regarding existing toolchain names in DB are performed. If some data is missing DB will be updated diff --git a/workspace_tools/test_mysql.py b/workspace_tools/test_mysql.py index 4a4ebd25af..5899bd12aa 100644 --- a/workspace_tools/test_mysql.py +++ b/workspace_tools/test_mysql.py @@ -29,6 +29,7 @@ class MySQLDBAccess(BaseDBAccess): """ def __init__(self): BaseDBAccess.__init__(self) + self.DB_TYPE = 'mysql' def detect_database(self, verbose=False): """ detect database and return VERION data structure or string (verbose=True) @@ -50,7 +51,7 @@ class MySQLDBAccess(BaseDBAccess): Function should return tuple with parsed (host, user, passwd, db) or None if error E.g. connection string: 'mysql://username:password@127.0.0.1/db_name' """ - result = BaseDBAccess.parse_db_connection_string(str) + result = BaseDBAccess().parse_db_connection_string(str) if result is not None: (db_type, username, password, host, db_name) = result if db_type != 'mysql': @@ -67,11 +68,36 @@ class MySQLDBAccess(BaseDBAccess): """ try: self.db_object = mdb.connect(host=host, user=user, passwd=passwd, db=db) - self.db_type = 'mysql' + # Let's remember connection credentials + self.db_type = self.DB_TYPE + self.host = host + self.user = user + self.passwd = passwd + self.db = db except mdb.Error, e: print "Error %d: %s"% (e.args[0], e.args[1]) self.db_object = None self.db_type = None + self.host = None + self.user = None + self.passwd = None + self.db = None + + def connect_url(self, db_url): + """ Connects to database using db_url (database url parsing), + store host, username, password, db_name + """ + result = self.parse_db_connection_string(db_url) + if result is not None: + (db_type, username, password, host, db_name) = result + if db_type == self.DB_TYPE: + self.connect(host, username, password, db_name) + + def reconnect(self): + """ Reconnects to DB and returns DB object using stored host name, + database name and credentials (user name and password) + """ + self.connect(self.host, self.user, self.passwd, self.db) def disconnect(self): """ Close DB connection @@ -107,15 +133,20 @@ class MySQLDBAccess(BaseDBAccess): con.commit() return cur.lastrowid - def get_next_build_id(self, name, desc=''): + def get_next_build_id(self, name, desc='', status=None): """ Insert new build_id (DB unique build like ID number to send all test results) """ - query = """INSERT INTO `%s` (%s_name, %s_desc) - VALUES ('%s', '%s')"""% (self.TABLE_BUILD_ID, - self.TABLE_BUILD_ID, - self.TABLE_BUILD_ID, - self.escape_string(name), - self.escape_string(desc)) + if status is None: + status = self.BUILD_ID_STATUS_STARTED + + query = """INSERT INTO `%s` (%s_name, %s_desc, %s_status_fk) + VALUES ('%s', '%s', %d)"""% (self.TABLE_BUILD_ID, + self.TABLE_BUILD_ID, + self.TABLE_BUILD_ID, + self.TABLE_BUILD_ID, + self.escape_string(name), + self.escape_string(desc), + status) index = self.insert(query) # Provide inserted record PK return index @@ -160,7 +191,30 @@ class MySQLDBAccess(BaseDBAccess): cur.execute("UNLOCK TABLES") return result - def insert_test_entry(self, next_build_id, target, toolchain, test_type, test_id, test_result, test_time, test_timeout, test_loop, test_extra=''): + def update_build_id_info(self, build_id, **kw): + """ Update additional data inside build_id table + Examples: + db.update_build_id_info(build_id, _status_fk=self.BUILD_ID_STATUS_COMPLETED, _shuffle_seed=0.0123456789): + """ + if len(kw): + con = self.db_object + cur = con.cursor() + # Prepare UPDATE query + # ["`mtest_build_id_pk`=[value-1]", "`mtest_build_id_name`=[value-2]", "`mtest_build_id_desc`=[value-3]"] + set_list = [] + for col_sufix in kw: + assign_str = "`%s%s`='%s'"% (self.TABLE_BUILD_ID, col_sufix, self.escape_string(str(kw[col_sufix]))) + set_list.append(assign_str) + set_str = ', '.join(set_list) + query = """UPDATE `%s` + SET %s + WHERE `mtest_build_id_pk`=%d"""% (self.TABLE_BUILD_ID, + set_str, + build_id) + cur.execute(query) + con.commit() + + def insert_test_entry(self, build_id, target, toolchain, test_type, test_id, test_result, test_time, test_timeout, test_loop, test_extra=''): """ Inserts test result entry to database. All checks regarding existing toolchain names in DB are performed. If some data is missing DB will be updated @@ -186,7 +240,7 @@ class MySQLDBAccess(BaseDBAccess): `mtest_test_loop_no`, `mtest_test_result_extra`) VALUES (%d, %d, %d, %d, %d, %d, %.2f, %.2f, %d, '%s')"""% (self.TABLE_TEST_ENTRY, - next_build_id, + build_id, target_fk, toolchain_fk, test_type_fk,