1from __future__ import print_function
2
3import os.path
4import sys
5import time
6import traceback
7
8from .. import Env
9
10_COLOR_CODES = {
11    'white': u'\033[0m',
12    'red':  u'\033[31m',
13    'green': u'\033[32m',
14    'orange': u'\033[33m',
15    'blue': u'\033[34m',
16    'purple': u'\033[35m',
17    'W': u'\033[0m',
18    'R': u'\033[31m',
19    'G': u'\033[32m',
20    'O': u'\033[33m',
21    'B': u'\033[34m',
22    'P': u'\033[35m'
23}
24
25
26def _get_log_file_name():
27    if Env.Env.CURRENT_LOG_FOLDER:
28        file_name = os.path.join(Env.Env.CURRENT_LOG_FOLDER, 'console.log')
29    else:
30        raise OSError('env log folder does not exist, will not save to log file')
31    return file_name
32
33
34def format_timestamp():
35    ts = time.time()
36    return '{}:{}'.format(time.strftime('%m-%d %H:%M:%S', time.localtime(ts)), str(ts % 1)[2:5])
37
38
39def console_log(data, color='white', end='\n'):
40    """
41    log data to console.
42    (if not flush console log, Gitlab-CI won't update logs during job execution)
43
44    :param data: data content
45    :param color: color
46    """
47    if color not in _COLOR_CODES:
48        color = 'white'
49    color_codes = _COLOR_CODES[color]
50    if isinstance(data, type(b'')):
51        data = data.decode('utf-8', 'replace')
52    print(color_codes + data, end=end)
53    if color not in ['white', 'W']:
54        # reset color to white for later logs
55        print(_COLOR_CODES['white'] + u'\r')
56    sys.stdout.flush()
57    log_data = '[{}] '.format(format_timestamp()) + data
58    try:
59        log_file = _get_log_file_name()
60        with open(log_file, 'a+') as f:
61            f.write(log_data + end)
62    except OSError:
63        pass
64
65
66__LOADED_MODULES = dict()
67# we should only load one module once.
68# if we load one module twice,
69# python will regard the same object loaded in the first time and second time as different objects.
70# it will lead to strange errors like `isinstance(object, type_of_this_object)` return False
71
72
73def load_source(path):
74    """
75    Dynamic loading python file. Note that this function SHOULD NOT be used to replace ``import``.
76    It should only be used when the package path is only available in runtime.
77
78    :param path: The path of python file
79    :return: Loaded object
80    """
81    path = os.path.realpath(path)
82    # load name need to be unique, otherwise it will update the already loaded module
83    load_name = str(len(__LOADED_MODULES))
84    try:
85        return __LOADED_MODULES[path]
86    except KeyError:
87        try:
88            dir = os.path.dirname(path)
89            sys.path.append(dir)
90            from importlib.machinery import SourceFileLoader
91            ret = SourceFileLoader(load_name, path).load_module()
92        except ImportError:
93            # importlib.machinery doesn't exists in Python 2 so we will use imp (deprecated in Python 3)
94            import imp
95            ret = imp.load_source(load_name, path)
96        finally:
97            sys.path.remove(dir)
98        __LOADED_MODULES[path] = ret
99        return ret
100
101
102def handle_unexpected_exception(junit_test_case, exception):
103    """
104    Helper to log & add junit result details for an unexpected exception encountered
105    when running a test case.
106
107    Should always be called from inside an except: block
108    """
109    traceback.print_exc()
110    # AssertionError caused by an 'assert' statement has an empty string as its 'str' form
111    e_str = str(exception) if str(exception) else repr(exception)
112    junit_test_case.add_failure_info('Unexpected exception: {}\n{}'.format(e_str, traceback.format_exc()))
113
114
115def format_case_id(case_name, target='esp32', config='default'):
116    return '{}.{}.{}'.format(target, config, case_name)
117