189 lines
6.0 KiB
Python
189 lines
6.0 KiB
Python
#! /usr/bin/env python3
|
|
|
|
import logging
|
|
import traceback
|
|
|
|
from functools import wraps
|
|
|
|
import time
|
|
from blinker import signal
|
|
|
|
|
|
def print_err(e, do_print=True):
|
|
"""will print python error if do_print is true.
|
|
|
|
Always call this function, but it will not run if turned off in config.
|
|
"""
|
|
if do_print:
|
|
print(e)
|
|
traceback.print_tb(e.__traceback__)
|
|
|
|
|
|
def print_command(cmd, do_print=False):
|
|
"""will print generate command line if do_print is true.
|
|
|
|
Always call this function, but it will not run if turned off in config.
|
|
"""
|
|
if do_print:
|
|
if type(cmd) is list:
|
|
try:
|
|
cmd = " ".join(cmd)
|
|
except Exception as e:
|
|
print_err(e, True)
|
|
print("ERROR: cannot convert cmd to string: {}".format(cmd))
|
|
return
|
|
|
|
if type(cmd) is str:
|
|
print("\n{}\n".format(cmd))
|
|
|
|
|
|
def run_module(module_name=None):
|
|
def run_module_inner(func, module_name=module_name):
|
|
"""decorator to catch exceptions in test_case.run_xx call
|
|
|
|
NOTE: ONLY for `test_case.run_xx(self, config, *args, **kargs)` calls.
|
|
which has no return value.
|
|
|
|
Not for dynasty calls, which is function calls with different parameters.
|
|
"""
|
|
|
|
@wraps(func)
|
|
def wrapper(self, *args, module_name=module_name, **kargs):
|
|
|
|
if module_name is None:
|
|
module_name = func.__name__
|
|
if "hw_mode" in kargs:
|
|
m_name = module_name.split("/")[-1]
|
|
hw_mode = kargs["hw_mode"]
|
|
module_name = f"kdp{hw_mode}/{m_name}"
|
|
|
|
# prepare parameters
|
|
config_ready = hasattr(self, "config")
|
|
# maybe: no self.config when initial
|
|
|
|
try:
|
|
print_time_stamp = config_ready and self.config["regression"]["generate_time_stamps"]
|
|
unit_to_denominator = {"second": 1, "minute": 60, "hour": 3600}
|
|
curr_unit = self.config["regression"]["time_stamps_unit"]
|
|
col_name = f"{module_name}:t"
|
|
except:
|
|
print_time_stamp = False
|
|
|
|
try:
|
|
if print_time_stamp:
|
|
start_time = time.perf_counter()
|
|
results = func(self, *args, **kargs)
|
|
# func may failed already
|
|
end_time = time.perf_counter()
|
|
total_time = (end_time - start_time) / unit_to_denominator[curr_unit]
|
|
signal("time_sender").send((self.model_id, col_name, int(total_time)))
|
|
else:
|
|
results = func(self, *args, **kargs)
|
|
|
|
signal("data_sender").send((self.model_id, module_name, "✓"))
|
|
return results
|
|
|
|
except Exception as e:
|
|
# try to get time as well
|
|
try:
|
|
if print_time_stamp:
|
|
end_time = time.perf_counter()
|
|
total_time = (end_time - start_time) / unit_to_denominator[curr_unit]
|
|
signal("time_sender").send((self.model_id, col_name, int(total_time)))
|
|
except Exception as e2:
|
|
pass
|
|
|
|
if type(e) is RegressionError:
|
|
raise
|
|
elif type(e) is MultiRegressionError:
|
|
raise
|
|
else:
|
|
print_err(e, config_ready and self.config["regression"]["print_error"])
|
|
raise RegressionError(module_name, self.model_id)
|
|
|
|
return wrapper
|
|
return run_module_inner
|
|
|
|
|
|
class MultiRegressionError(Exception):
|
|
def __init__(self, error_list):
|
|
# TODO: what if just general error?
|
|
|
|
self.errors = [e for e in error_list if type(e) is RegressionError]
|
|
|
|
|
|
class GeneralError(Exception):
|
|
"""use this GeneralError to send error test_case.py for better hanle"""
|
|
def __init__(self, msg, details=None):
|
|
self.msg = msg
|
|
self.details = details
|
|
|
|
|
|
class RegressionError(Exception):
|
|
"""Customized Exception for flow error recording.
|
|
|
|
If a moduel failed, it will raise this exception with info.
|
|
Exceptions will be recorded in report.
|
|
|
|
* Dynasty related module and "Success" are defined manually.
|
|
* other modules are using the corresponding function names.
|
|
|
|
"""
|
|
|
|
|
|
def __init__(self, module_name, model_name, msg=None, print_err=False):
|
|
"""Initial RegressionError by give certain infomation for future record.
|
|
|
|
- module_name: knerex updater 520, dynasty 520wq, etc
|
|
- model_name: is the model name running regression
|
|
- msg: is the brief message to print in summary report
|
|
"""
|
|
if print_err:
|
|
traceback.print_exc()
|
|
|
|
self.module_name = module_name
|
|
self.model_name = model_name
|
|
self.msg = msg
|
|
|
|
if module_name == "general/Success":
|
|
flag = "Success"
|
|
else:
|
|
flag = "Failure"
|
|
|
|
err_msg = '{} for model "{}" when running "{}"'.format(flag, model_name, module_name)
|
|
|
|
if flag == "Failure":
|
|
red = "\033[91m {}\033[00m"
|
|
err_msg = red.format(err_msg)
|
|
else:
|
|
green = "\033[92m {}\033[00m"
|
|
err_msg = green.format(err_msg)
|
|
|
|
logging.getLogger().error(err_msg, exc_info=False)
|
|
|
|
# usually we will print command. below info are redundant.
|
|
# if command is not None:
|
|
# logging.getLogger().debug(command, exc_info=False)
|
|
|
|
|
|
def excepthook_print_last(etype, value, tb):
|
|
"""Print last exception, instead of all.
|
|
|
|
To use, run:
|
|
> sys.excepthook = excepthook_print_last
|
|
"""
|
|
traceback_list = traceback.format_exception(etype, value, tb)
|
|
print(traceback_list[-1])
|
|
|
|
stack_summary = traceback.extract_tb(tb)
|
|
last_frame = stack_summary[-1]
|
|
filename, lineno, name, line = last_frame
|
|
|
|
print(f"File: {filename}, Line: {lineno}")
|
|
print(f"Code: {line}")
|
|
|
|
# print all variables. not needed now
|
|
# frame = tb.tb_frame
|
|
# for key, value in frame.f_locals.items():
|
|
# print(f"{key} = {value}")
|