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

View File

@ -18,6 +18,7 @@ Author: Przemyslaw Wirkus <Przemyslaw.wirkus@arm.com>
"""
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

View File

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