Database functionality improved: Changes in database structure, added new features like: we update MUTs, test specification, test shuffle seed if needed and update test suite single run status at the beggining and in the end of test run

pull/475/head
Przemek Wirkus 2014-08-19 11:04:09 +01:00
parent 24a73e3c14
commit 9f4946651a
3 changed files with 149 additions and 14 deletions

View File

@ -197,8 +197,21 @@ class SingleTestRunner(object):
self.opts_jobs = _opts_jobs if _opts_jobs is not None else 1 self.opts_jobs = _opts_jobs if _opts_jobs is not None else 1
self.opts_extend_test_timeout = _opts_extend_test_timeout 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 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 = 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): def shuffle_random_func(self):
return self.shuffle_random_seed return self.shuffle_random_seed
@ -251,6 +264,18 @@ class SingleTestRunner(object):
test_map_keys = TEST_MAP.keys() test_map_keys = TEST_MAP.keys()
if self.opts_shuffle_test_order: if self.opts_shuffle_test_order:
random.shuffle(test_map_keys, self.shuffle_random_func) 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: for test_id in test_map_keys:
test = TEST_MAP[test_id] test = TEST_MAP[test_id]
@ -334,9 +359,17 @@ class SingleTestRunner(object):
# which the test gets interrupted # which the test gets interrupted
test_spec = self.shape_test_request(target, path, test_id, test_duration) test_spec = self.shape_test_request(target, path, test_id, test_duration)
test_loops = self.get_test_loop_count(test_id) 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) single_test_result = self.handle(test_spec, target, toolchain, test_loops=test_loops)
if single_test_result is not None: if single_test_result is not None:
test_summary.append(single_test_result) 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 return test_summary, self.shuffle_random_seed
def generate_test_summary_by_target(self, test_summary, shuffle_seed=None): 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('\\'): if not disk.endswith('/') and not disk.endswith('\\'):
disk += '/' disk += '/'
if self.db_logger:
self.db_logger.reconnect()
# Tests can be looped so test results must be stored for the same test # Tests can be looped so test results must be stored for the same test
test_all_result = [] test_all_result = []
for test_index in range(test_loops): for test_index in range(test_loops):
@ -621,10 +657,26 @@ class SingleTestRunner(object):
# Store test result # Store test result
test_all_result.append(single_test_result) test_all_result.append(single_test_result)
elapsed_time = time() - start_host_exec_time elapsed_time = time() - start_host_exec_time
print self.print_test_result(single_test_result, target_name, toolchain_name, print self.print_test_result(single_test_result, target_name, toolchain_name,
test_id, test_description, elapsed_time, duration) 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, return (self.shape_global_test_loop_result(test_all_result), target_name, toolchain_name,
test_id, test_description, round(elapsed_time, 2), test_id, test_description, round(elapsed_time, 2),
duration, self.shape_test_loop_ok_result_count(test_all_result)) duration, self.shape_test_loop_ok_result_count(test_all_result))
@ -1159,7 +1211,7 @@ class CLITestLogger(TestLogger):
return timestamp_str + log_line_str return timestamp_str + log_line_str
def log_line(self, LogType, log_line, timestamp=True, line_delim='\n'): 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 at the end of log file
""" """
log_entry = TestLogger.log_line(self, LogType, log_line) log_entry = TestLogger.log_line(self, LogType, log_line)

View File

@ -18,6 +18,7 @@ Author: Przemyslaw Wirkus <Przemyslaw.wirkus@arm.com>
""" """
import re import re
import json
class BaseDBAccess(): class BaseDBAccess():
@ -26,6 +27,12 @@ class BaseDBAccess():
def __init__(self): def __init__(self):
self.db_object = None self.db_object = None
self.db_type = 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) # Test Suite DB scheme (table names)
self.TABLE_BUILD_ID = 'mtest_build_id' self.TABLE_BUILD_ID = 'mtest_build_id'
self.TABLE_BUILD_ID_STATUS = 'mtest_build_id_status' self.TABLE_BUILD_ID_STATUS = 'mtest_build_id_status'
@ -67,6 +74,9 @@ class BaseDBAccess():
""" Parsing SQL DB connection string. String should contain: """ Parsing SQL DB connection string. String should contain:
- DB Name, user name, password, URL (DB host), name - 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 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' E.g. connection string: 'mysql://username:password@127.0.0.1/db_name'
""" """
result = None result = None
@ -87,6 +97,18 @@ class BaseDBAccess():
""" """
pass 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): def disconnect(self):
""" Close DB connection """ Close DB connection
""" """
@ -124,7 +146,14 @@ class BaseDBAccess():
""" """
pass 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 """ Inserts test result entry to database. All checks regarding existing
toolchain names in DB are performed. toolchain names in DB are performed.
If some data is missing DB will be updated If some data is missing DB will be updated

View File

@ -29,6 +29,7 @@ class MySQLDBAccess(BaseDBAccess):
""" """
def __init__(self): def __init__(self):
BaseDBAccess.__init__(self) BaseDBAccess.__init__(self)
self.DB_TYPE = 'mysql'
def detect_database(self, verbose=False): def detect_database(self, verbose=False):
""" detect database and return VERION data structure or string (verbose=True) """ 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 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' 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: if result is not None:
(db_type, username, password, host, db_name) = result (db_type, username, password, host, db_name) = result
if db_type != 'mysql': if db_type != 'mysql':
@ -67,11 +68,36 @@ class MySQLDBAccess(BaseDBAccess):
""" """
try: try:
self.db_object = mdb.connect(host=host, user=user, passwd=passwd, db=db) 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: except mdb.Error, e:
print "Error %d: %s"% (e.args[0], e.args[1]) print "Error %d: %s"% (e.args[0], e.args[1])
self.db_object = None self.db_object = None
self.db_type = 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): def disconnect(self):
""" Close DB connection """ Close DB connection
@ -107,15 +133,20 @@ class MySQLDBAccess(BaseDBAccess):
con.commit() con.commit()
return cur.lastrowid 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) """ Insert new build_id (DB unique build like ID number to send all test results)
""" """
query = """INSERT INTO `%s` (%s_name, %s_desc) if status is None:
VALUES ('%s', '%s')"""% (self.TABLE_BUILD_ID, status = self.BUILD_ID_STATUS_STARTED
self.TABLE_BUILD_ID,
self.TABLE_BUILD_ID, query = """INSERT INTO `%s` (%s_name, %s_desc, %s_status_fk)
self.escape_string(name), VALUES ('%s', '%s', %d)"""% (self.TABLE_BUILD_ID,
self.escape_string(desc)) 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 index = self.insert(query) # Provide inserted record PK
return index return index
@ -160,7 +191,30 @@ class MySQLDBAccess(BaseDBAccess):
cur.execute("UNLOCK TABLES") cur.execute("UNLOCK TABLES")
return result 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 """ Inserts test result entry to database. All checks regarding existing
toolchain names in DB are performed. toolchain names in DB are performed.
If some data is missing DB will be updated If some data is missing DB will be updated
@ -186,7 +240,7 @@ class MySQLDBAccess(BaseDBAccess):
`mtest_test_loop_no`, `mtest_test_loop_no`,
`mtest_test_result_extra`) `mtest_test_result_extra`)
VALUES (%d, %d, %d, %d, %d, %d, %.2f, %.2f, %d, '%s')"""% (self.TABLE_TEST_ENTRY, VALUES (%d, %d, %d, %d, %d, %d, %.2f, %.2f, %d, '%s')"""% (self.TABLE_TEST_ENTRY,
next_build_id, build_id,
target_fk, target_fk,
toolchain_fk, toolchain_fk,
test_type_fk, test_type_fk,