mirror of https://github.com/ARMmbed/mbed-os.git
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
parent
24a73e3c14
commit
9f4946651a
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue