2026-01-28 06:16:04 +00:00

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}")