openubmc-bingo 0.6.45__py3-none-any.whl → 0.6.99__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bmcgo/__init__.py +1 -1
- bmcgo/bmcgo.py +9 -3
- bmcgo/bmcgo_config.py +16 -0
- bmcgo/cli/cli.py +72 -21
- bmcgo/codegen/__init__.py +1 -1
- bmcgo/codegen/lua/codegen.py +2 -2
- bmcgo/codegen/lua/script/check_intfs.py +1 -0
- bmcgo/codegen/lua/script/dto/options.py +1 -0
- bmcgo/codegen/lua/script/gen_db_json.py +4 -3
- bmcgo/codegen/lua/script/gen_rpc_msg_json.py +78 -11
- bmcgo/codegen/lua/script/model_consistency_check.py +1 -1
- bmcgo/codegen/lua/script/render_utils/db_lua.py +5 -6
- bmcgo/codegen/lua/script/render_utils/model_lua.py +5 -1
- bmcgo/codegen/lua/script/template.py +5 -0
- bmcgo/codegen/lua/script/utils.py +50 -8
- bmcgo/codegen/lua/templates/apps/Makefile +2 -2
- bmcgo/codegen/lua/templates/apps/client.lua.mako +1 -1
- bmcgo/codegen/lua/templates/apps/model.lua.mako +4 -3
- bmcgo/codegen/lua/templates/apps/service.lua.mako +1 -1
- bmcgo/codegen/lua/templates/apps/utils/mdb_intf.lua.mako +4 -0
- bmcgo/codegen/lua/templates/new_app_v2/CMakeLists.txt.mako +26 -0
- bmcgo/codegen/lua/templates/new_app_v2/conanfile.py.mako +9 -0
- bmcgo/codegen/lua/v1/script/render_utils/db_lua.py +5 -6
- bmcgo/codegen/lua/v1/script/render_utils/model_lua.py +13 -1
- bmcgo/codegen/lua/v1/templates/apps/client.lua.mako +1 -1
- bmcgo/codegen/lua/v1/templates/apps/local_db.lua.mako +0 -4
- bmcgo/codegen/lua/v1/templates/apps/message.lua.mako +3 -0
- bmcgo/codegen/lua/v1/templates/apps/model.lua.mako +3 -0
- bmcgo/codegen/lua/v1/templates/apps/utils/mdb_intf.lua.mako +6 -4
- bmcgo/component/analysis/analysis.py +9 -4
- bmcgo/component/analysis/dep-rules.json +20 -8
- bmcgo/component/analysis/dep_node.py +2 -0
- bmcgo/component/analysis/intf_validation.py +8 -7
- bmcgo/component/analysis/sr_validation.py +5 -4
- bmcgo/component/busctl_log_parse/busctl_log_parser.py +809 -0
- bmcgo/component/busctl_log_parse/mock_data_save.py +170 -0
- bmcgo/component/busctl_log_parse/test_data_save.py +49 -0
- bmcgo/component/component_helper.py +29 -0
- bmcgo/component/coverage/incremental_cov.py +5 -0
- bmcgo/component/fixture/__init__.py +29 -0
- bmcgo/component/fixture/auto_case_generator.py +490 -0
- bmcgo/component/fixture/busctl_type_converter.py +1081 -0
- bmcgo/component/fixture/common_config.py +15 -0
- bmcgo/component/fixture/dbus_gateway.py +669 -0
- bmcgo/component/fixture/dbus_library.py +250 -0
- bmcgo/component/fixture/dbus_mock_utils.py +514 -0
- bmcgo/component/fixture/dbus_response_handler.py +138 -0
- bmcgo/component/fixture/dbus_signature.py +110 -0
- bmcgo/component/template_v2/conanbase.py.mako +1 -5
- bmcgo/component/test.py +69 -10
- bmcgo/error_analyzer/__init__.py +0 -0
- bmcgo/error_analyzer/case_matcher.py +114 -0
- bmcgo/error_analyzer/log_parser.py +128 -0
- bmcgo/error_analyzer/unified_error_analyzer.py +359 -0
- bmcgo/error_cases/cases.yml +59 -0
- bmcgo/error_cases/cases_template_valid.json +71 -0
- bmcgo/error_cases/conanfile.py +58 -0
- bmcgo/frame.py +0 -4
- bmcgo/functional/analysis.py +18 -12
- bmcgo/functional/bmc_studio_action.py +21 -10
- bmcgo/functional/check.py +86 -42
- bmcgo/functional/conan_index_build.py +1 -1
- bmcgo/functional/config.py +22 -18
- bmcgo/functional/csr_build.py +63 -34
- bmcgo/functional/deploy.py +4 -3
- bmcgo/functional/diff.py +51 -34
- bmcgo/functional/full_component.py +16 -5
- bmcgo/functional/hpm_signer.py +484 -0
- bmcgo/functional/new.py +8 -2
- bmcgo/functional/schema_valid.py +111 -15
- bmcgo/functional/upgrade.py +6 -6
- bmcgo/misc.py +1 -0
- bmcgo/tasks/task_build_conan.py +27 -6
- bmcgo/tasks/task_build_rootfs_img.py +120 -83
- bmcgo/tasks/task_buildgppbin.py +30 -13
- bmcgo/tasks/task_buildhpm_ext4.py +5 -3
- bmcgo/tasks/task_download_buildtools.py +20 -11
- bmcgo/tasks/task_download_dependency.py +29 -20
- bmcgo/tasks/task_hpm_envir_prepare.py +32 -53
- bmcgo/tasks/task_packet_to_supporte.py +12 -4
- bmcgo/tasks/task_prepare.py +1 -1
- bmcgo/tasks/task_sign_and_pack_hpm.py +15 -7
- bmcgo/utils/component_version_check.py +4 -4
- bmcgo/utils/config.py +3 -0
- bmcgo/utils/fetch_component_code.py +148 -17
- bmcgo/utils/install_manager.py +2 -2
- bmcgo/utils/installations/base_installer.py +10 -27
- bmcgo/utils/installations/install_plans/studio.yml +3 -0
- bmcgo/utils/mapping_config_patch.py +5 -4
- bmcgo/utils/tools.py +49 -7
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/METADATA +1 -1
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/RECORD +95 -74
- bmcgo/tasks/download_buildtools_hm.py +0 -124
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/WHEEL +0 -0
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/entry_points.txt +0 -0
- {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
|
|
4
|
+
# openUBMC is licensed under Mulan PSL v2.
|
|
5
|
+
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
6
|
+
# You may obtain a copy of Mulan PSL v2 at:
|
|
7
|
+
# http://license.coscl.org.cn/MulanPSL2
|
|
8
|
+
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
9
|
+
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
10
|
+
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
11
|
+
# See the Mulan PSL v2 for more details.
|
|
12
|
+
from dbus_next import Variant
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DBusSignature:
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def get_dbus_signature(value):
|
|
19
|
+
"""获取D-Bus值的签名
|
|
20
|
+
|
|
21
|
+
dbus-next使用Python原生类型表示D-Bus类型,这里基于值类型推断签名
|
|
22
|
+
"""
|
|
23
|
+
if isinstance(value, bool):
|
|
24
|
+
return 'b' # boolean
|
|
25
|
+
elif isinstance(value, int):
|
|
26
|
+
# 根据值范围推断整数类型
|
|
27
|
+
if value >= 0 and value <= 255:
|
|
28
|
+
return 'y' # byte
|
|
29
|
+
elif value >= -32768 and value <= 32767:
|
|
30
|
+
# 注意:先检查正值范围,确保非负整数被正确识别为uint16
|
|
31
|
+
if value >= 0:
|
|
32
|
+
return 'q' # uint16
|
|
33
|
+
else:
|
|
34
|
+
return 'n' # int16
|
|
35
|
+
elif value >= 0 and value <= 65535:
|
|
36
|
+
return 'q' # uint16
|
|
37
|
+
elif value >= -2147483648 and value <= 2147483647:
|
|
38
|
+
if value >= 0:
|
|
39
|
+
return 'u' # uint32
|
|
40
|
+
else:
|
|
41
|
+
return 'i' # int32
|
|
42
|
+
elif value >= 0 and value <= 4294967295:
|
|
43
|
+
return 'u' # uint32
|
|
44
|
+
elif value >= -9223372036854775808 and value <= 9223372036854775807:
|
|
45
|
+
if value >= 0:
|
|
46
|
+
return 't' # uint64
|
|
47
|
+
else:
|
|
48
|
+
return 'x' # int64
|
|
49
|
+
elif value >= 0 and value <= 18446744073709551615:
|
|
50
|
+
return 't' # uint64
|
|
51
|
+
else:
|
|
52
|
+
raise ValueError(f"Integer value {value} out of range for DBus types")
|
|
53
|
+
elif isinstance(value, float):
|
|
54
|
+
return 'd' # double
|
|
55
|
+
elif isinstance(value, bytes):
|
|
56
|
+
return 'ay' # array of bytes
|
|
57
|
+
elif isinstance(value, str):
|
|
58
|
+
# 检查是否是对象路径
|
|
59
|
+
if value.startswith('/') and '//' not in value:
|
|
60
|
+
return 'o' # object path
|
|
61
|
+
# 暂时移除对签名的自动检测,避免普通字符串被错误识别为签名
|
|
62
|
+
# 在实际应用中,我们应该只在明确是签名的情况下才返回'g'
|
|
63
|
+
return 's' # 默认为string类型
|
|
64
|
+
elif isinstance(value, list):
|
|
65
|
+
# 首先检查是否是混合类型列表,如果是则视为结构体
|
|
66
|
+
if len(value) > 0:
|
|
67
|
+
# 获取所有元素的类型
|
|
68
|
+
types = set(type(item) for item in value)
|
|
69
|
+
# 如果有多种类型,或者包含字符串和数字的混合,视为结构体
|
|
70
|
+
if len(types) > 1 or (int in types and str in types):
|
|
71
|
+
inner_sig = ''.join([DBusSignature.get_dbus_signature(elem) for elem in value])
|
|
72
|
+
return f'({inner_sig})' # struct
|
|
73
|
+
|
|
74
|
+
# 特殊处理结构体:如果列表内容看起来像结构体(混合类型或含有复杂类型)
|
|
75
|
+
if all(isinstance(item, (str, bytes)) for item in value) and \
|
|
76
|
+
any(isinstance(item, bytes) for item in value):
|
|
77
|
+
# 这看起来像一个结构体(包含字符串和字节数组)
|
|
78
|
+
inner_sig = ''.join([DBusSignature.get_dbus_signature(elem) for elem in value])
|
|
79
|
+
return f'({inner_sig})' # struct
|
|
80
|
+
# 普通数组处理
|
|
81
|
+
if value:
|
|
82
|
+
# 检查是否是整数列表
|
|
83
|
+
if all(isinstance(item, int) for item in value):
|
|
84
|
+
# 整数列表应该被识别为整数数组(ai),而不是字节数组(ay)
|
|
85
|
+
return 'ai'
|
|
86
|
+
# 假设所有元素类型相同
|
|
87
|
+
elem_sig = DBusSignature.get_dbus_signature(value[0])
|
|
88
|
+
# 移除自动将整数列表转换为字节数组的逻辑,因为dbus-next要求字节数组必须是bytes类型
|
|
89
|
+
# 只有当明确传入bytes类型时才返回ay(这在前面的代码中已经处理)
|
|
90
|
+
return f'a{elem_sig}'
|
|
91
|
+
return 'av' # 默认为variant数组
|
|
92
|
+
elif isinstance(value, dict):
|
|
93
|
+
if value:
|
|
94
|
+
key, val = next(iter(value.items()))
|
|
95
|
+
key_sig = DBusSignature.get_dbus_signature(key)
|
|
96
|
+
# 特殊处理variant值
|
|
97
|
+
if isinstance(val, Variant):
|
|
98
|
+
val_sig = 'v'
|
|
99
|
+
else:
|
|
100
|
+
val_sig = DBusSignature.get_dbus_signature(val)
|
|
101
|
+
return f'a{{{key_sig}{val_sig}}}'
|
|
102
|
+
return 'a{sv}' # 默认为string:variant字典
|
|
103
|
+
elif isinstance(value, tuple):
|
|
104
|
+
# 结构体签名需要用括号包围
|
|
105
|
+
inner_sig = ''.join([DBusSignature.get_dbus_signature(elem) for elem in value])
|
|
106
|
+
return f'({inner_sig})' # struct
|
|
107
|
+
elif isinstance(value, Variant):
|
|
108
|
+
return 'v'
|
|
109
|
+
else:
|
|
110
|
+
return 's' # 默认视为字符串
|
|
@@ -214,11 +214,7 @@ class ConanBase(ConanFile):
|
|
|
214
214
|
os.path.join(self.package_folder, "usr/share/doc/openubmc/${pkg.name}/docs")),
|
|
215
215
|
("*", os.path.join(self.source_folder, "build"),
|
|
216
216
|
os.path.join(self.package_folder, "include")),
|
|
217
|
-
("permissions.ini", os.path.join(self.source_folder, "dist"), self.package_folder)
|
|
218
|
-
("*.md", self.source_folder, os.path.join(self.package_folder,
|
|
219
|
-
"usr/share/doc/openubmc/${pkg.name}/docs")),
|
|
220
|
-
("*.MD", self.source_folder, os.path.join(self.package_folder,
|
|
221
|
-
"usr/share/doc/openubmc/${pkg.name}/docs"))
|
|
217
|
+
("permissions.ini", os.path.join(self.source_folder, "dist"), self.package_folder)
|
|
222
218
|
]
|
|
223
219
|
for pattern, src, dst in files_to_copy:
|
|
224
220
|
copy(self, pattern, src=src, dst=dst, keep_path=True)
|
bmcgo/component/test.py
CHANGED
|
@@ -33,6 +33,7 @@ from bmcgo.component.coverage.incremental_cov import IncrementalCov
|
|
|
33
33
|
from bmcgo.bmcgo_config import BmcgoConfig
|
|
34
34
|
from bmcgo.utils.tools import Tools
|
|
35
35
|
from bmcgo.errors import BmcGoException
|
|
36
|
+
from bmcgo.component.busctl_log_parse.busctl_log_parser import BusCtlLogParser
|
|
36
37
|
|
|
37
38
|
log = Logger("comp_test")
|
|
38
39
|
cwd = os.getcwd()
|
|
@@ -97,6 +98,15 @@ class TestComp():
|
|
|
97
98
|
self.origin_cov = "luacov.stats.out"
|
|
98
99
|
self.cov_filter = "luacov.stats.filter"
|
|
99
100
|
self.cov_report = "luacov.report.html"
|
|
101
|
+
# robot-test定义
|
|
102
|
+
self.robot_gen = self.args.robot_gen
|
|
103
|
+
self.robot_test = self.args.robot_test
|
|
104
|
+
self.log_file = self.args.log_file
|
|
105
|
+
if self.robot_gen and (not self.log_file or not os.path.isdir(self.log_file)):
|
|
106
|
+
raise BmcGoException("未指定 \"--log_file\" 参数,或该参数指定的目录不存在")
|
|
107
|
+
self.test_service = "bmc.kepler." + self.info.name
|
|
108
|
+
self.robot_output_dir = os.path.join(self.folder, "test/robot_it/test_db")
|
|
109
|
+
self.rt_folder = os.path.join(self.folder, "test/robot_it/")
|
|
100
110
|
# dt-fuzz定义
|
|
101
111
|
self.fuzz_test = self.args.fuzz_test
|
|
102
112
|
self.fuzz_gen = self.args.fuzz_gen
|
|
@@ -130,10 +140,13 @@ class TestComp():
|
|
|
130
140
|
parser.add_argument("-it", "--integration_test", help="Enable integration test", action=misc.STORE_TRUE)
|
|
131
141
|
parser.add_argument("-ft", "--fuzz_test", help="Enable fuzz test", action=misc.STORE_TRUE)
|
|
132
142
|
parser.add_argument("-fg", "--fuzz_gen", help="Generate fuzz case", action=misc.STORE_TRUE)
|
|
143
|
+
parser.add_argument("-rt", "--robot_test", help="Enable robot test", action=misc.STORE_TRUE)
|
|
144
|
+
parser.add_argument("-rg", "--robot_gen", help="Generate robot case", action=misc.STORE_TRUE)
|
|
145
|
+
parser.add_argument("-lf", "--log_file", help="Monitor log file directory path")
|
|
133
146
|
parser.add_argument("-cnt", "--fuzz_count", help="Fuzz count", required=False, type=int, default=100000)
|
|
134
147
|
parser.add_argument("-f", "--test_filter", help="Run unit test with a filter", required=False, default='.')
|
|
135
148
|
parser.add_argument("-a", "--app", help="App in hica", required=False, default='all')
|
|
136
|
-
parser.add_argument("--coverage_exclude", help="Specify coverage exclude file path of whitelist",
|
|
149
|
+
parser.add_argument("--coverage_exclude", help="Specify coverage exclude file path of whitelist",
|
|
137
150
|
required=False, default="")
|
|
138
151
|
return parser
|
|
139
152
|
|
|
@@ -200,7 +213,6 @@ class TestComp():
|
|
|
200
213
|
missed_lines = total_lines - hit_lines
|
|
201
214
|
return (f"{coverage}%", hit_lines, missed_lines)
|
|
202
215
|
|
|
203
|
-
|
|
204
216
|
@staticmethod
|
|
205
217
|
def _clear_result(file):
|
|
206
218
|
if not os.path.isfile(file):
|
|
@@ -238,9 +250,18 @@ class TestComp():
|
|
|
238
250
|
if results:
|
|
239
251
|
summary = f"# Ran {total} tests in {duration:.3f} seconds, {total - failed} successes, {failed} failures"
|
|
240
252
|
results.append(summary)
|
|
241
|
-
|
|
242
253
|
return results
|
|
243
254
|
|
|
255
|
+
def generate_robot(self, test_service, log_file, output_dir):
|
|
256
|
+
parser = BusCtlLogParser(test_service=test_service)
|
|
257
|
+
out_dir = os.path.join(output_dir, self.info.name)
|
|
258
|
+
parser.parse_dbus_log(log_file, out_dir)
|
|
259
|
+
dir_os = os.path.dirname(os.path.abspath(__file__))
|
|
260
|
+
Helper.run(["/usr/bin/env", "python3", os.path.join(dir_os, "fixture", "auto_case_generator.py"),
|
|
261
|
+
"--bmc-test-db-dir", output_dir, "--test-db-name", self.info.name,
|
|
262
|
+
"--fixture-dir", dir_os
|
|
263
|
+
])
|
|
264
|
+
|
|
244
265
|
def get_excluded_files_on_key(self, lang):
|
|
245
266
|
if not self.coverage_exclude:
|
|
246
267
|
return []
|
|
@@ -719,6 +740,17 @@ class TestComp():
|
|
|
719
740
|
|
|
720
741
|
def run_independent_test(self, test_env):
|
|
721
742
|
log.info("================ 测试 %s 开始 ================", self.current_app)
|
|
743
|
+
if not os.getenv("DBUS_SESSION_BUS_ADDRESS"):
|
|
744
|
+
result = self.tools.run_command(["dbus-launch", "--auto-syntax"], capture_output=True)
|
|
745
|
+
for line in result.stdout.strip().split(";"):
|
|
746
|
+
if '=' in line:
|
|
747
|
+
key, value = line.split('=', 1)
|
|
748
|
+
os.environ[key.strip()] = value.strip().strip("'").strip(';')
|
|
749
|
+
if self.robot_test:
|
|
750
|
+
self.tools.run_command(["robot", "-v", f"PROJECT_DIR:{self.folder}", "-v", f"DBUS_ADDRESS:{os.getenv("DBUS_SESSION_BUS_ADDRESS")}", "-o",
|
|
751
|
+
os.path.join(self.rt_folder, "results/output.xml"), "-l", os.path.join(self.rt_folder, "results/log.xml"),
|
|
752
|
+
"-r", os.path.join(self.rt_folder, "results/report.xml"), os.path.join(self.folder, "test/robot_it/")], show_log=True)
|
|
753
|
+
|
|
722
754
|
# 配置额外的环境变量
|
|
723
755
|
self.set_additional_env(test_env, self.folder, self.current_app)
|
|
724
756
|
test_entry = os.path.join(self.folder, "test/unit/test.lua")
|
|
@@ -846,6 +878,7 @@ class TestComp():
|
|
|
846
878
|
ljust_width = 30
|
|
847
879
|
log.info("%s %s", "单元测试(ut):".ljust(ljust_width), str(self.unit_test))
|
|
848
880
|
log.info("%s %s", "集成测试(it):".ljust(ljust_width), str(self.integration_test))
|
|
881
|
+
log.info("%s %s", "自动测试(rt):".ljust(ljust_width), str(self.robot_test))
|
|
849
882
|
log.info("%s %s", "覆盖率:".ljust(ljust_width), str(self.coverage))
|
|
850
883
|
log.info("%s %s", "地址消毒:".ljust(ljust_width), str(self.asan))
|
|
851
884
|
log.info("============================================================================")
|
|
@@ -876,8 +909,31 @@ class TestComp():
|
|
|
876
909
|
|
|
877
910
|
def check_folder(self):
|
|
878
911
|
test_package_folder = os.path.join(self.folder, "test_package")
|
|
879
|
-
|
|
880
|
-
|
|
912
|
+
flag_ut_path = os.path.isdir(os.path.join(self.folder, "test", "unit"))
|
|
913
|
+
flag_it_path = os.path.isdir(os.path.join(self.folder, "test", "integration"))
|
|
914
|
+
flag_fz_path = os.path.isdir(os.path.join(self.folder, "test", "fuzz"))
|
|
915
|
+
flag_rt_path = os.path.isdir(os.path.join(self.folder, "test", "robot_it"))
|
|
916
|
+
|
|
917
|
+
if self.fuzz_gen or self.robot_gen:
|
|
918
|
+
if self.fuzz_gen and not flag_fz_path:
|
|
919
|
+
log.error("fuzz 测试目录不存在!请检查当前组件的test目录下是否存在fuzz目录!")
|
|
920
|
+
return False
|
|
921
|
+
elif self.robot_gen and not flag_rt_path:
|
|
922
|
+
log.error("自动测试目录不存在!请检查当前组件的test目录下是否存在robot目录!")
|
|
923
|
+
return False
|
|
924
|
+
else:
|
|
925
|
+
return True
|
|
926
|
+
|
|
927
|
+
has_dt_type = self.unit_test or self.integration_test or self.fuzz_test or self.robot_test
|
|
928
|
+
if not has_dt_type:
|
|
929
|
+
self.unit_test = True
|
|
930
|
+
self.unit_test = flag_ut_path and self.unit_test
|
|
931
|
+
self.integration_test = flag_it_path and self.integration_test
|
|
932
|
+
self.fuzz_test = flag_fz_path and self.fuzz_test
|
|
933
|
+
self.robot_test = flag_rt_path and self.robot_test
|
|
934
|
+
has_dt_type = self.unit_test or self.integration_test or self.fuzz_test or self.robot_test
|
|
935
|
+
|
|
936
|
+
if os.path.isdir(test_package_folder) or has_dt_type:
|
|
881
937
|
return True
|
|
882
938
|
return False
|
|
883
939
|
|
|
@@ -894,17 +950,20 @@ class TestComp():
|
|
|
894
950
|
def run(self):
|
|
895
951
|
self.print_build_menu()
|
|
896
952
|
if not self.check_folder():
|
|
897
|
-
log.warning("
|
|
953
|
+
log.warning("当前组件不存在开发者测试的相关文件夹,开发者测试终止!")
|
|
954
|
+
return
|
|
955
|
+
|
|
956
|
+
# --robot-gen自动生成测试用例和打桩数据
|
|
957
|
+
if self.robot_gen:
|
|
958
|
+
if not os.path.isdir(self.robot_output_dir):
|
|
959
|
+
os.makedirs(self.robot_output_dir)
|
|
960
|
+
self.generate_robot(self.test_service, self.log_file, self.robot_output_dir)
|
|
898
961
|
return
|
|
899
962
|
|
|
900
963
|
# -gen代码自动生成逻辑
|
|
901
964
|
if self.fuzz_gen:
|
|
902
965
|
self.generate_fuzz()
|
|
903
966
|
return
|
|
904
|
-
# 构建完后单独运行DT
|
|
905
|
-
has_dt_type = self.unit_test or self.integration_test or self.fuzz_test
|
|
906
|
-
if not has_dt_type:
|
|
907
|
-
self.unit_test = True
|
|
908
967
|
|
|
909
968
|
# 构建被测组件
|
|
910
969
|
if not self.without_build:
|
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
|
|
4
|
+
# openUBMC is licensed under Mulan PSL v2.
|
|
5
|
+
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
6
|
+
# You may obtain a copy of Mulan PSL v2 at:
|
|
7
|
+
# http://license.coscl.org.cn/MulanPSL2
|
|
8
|
+
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
9
|
+
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
10
|
+
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
11
|
+
# See the Mulan PSL v2 for more details.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CaseMatcher:
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.rules = self.load_matching_rules()
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def fill_template(template, log_entry):
|
|
20
|
+
"""填充模板中的占位符"""
|
|
21
|
+
import re
|
|
22
|
+
|
|
23
|
+
def replace_placeholder(match):
|
|
24
|
+
placeholder = match.group(1)
|
|
25
|
+
if placeholder.startswith("log."):
|
|
26
|
+
path = placeholder[4:].split(".")
|
|
27
|
+
value = log_entry
|
|
28
|
+
for key in path:
|
|
29
|
+
if isinstance(value, dict):
|
|
30
|
+
value = value.get(key, "")
|
|
31
|
+
else:
|
|
32
|
+
value = getattr(value, key, "")
|
|
33
|
+
return str(value)
|
|
34
|
+
return match.group(0)
|
|
35
|
+
|
|
36
|
+
return re.sub(r"\{\{(\w+(?:\.\w+)*)\}\}", replace_placeholder, template)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def load_matching_rules():
|
|
40
|
+
"""加载匹配规则(可扩展)"""
|
|
41
|
+
return {}
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def check_single_rule(log_entry, rule):
|
|
45
|
+
"""检查单个匹配规则"""
|
|
46
|
+
field = rule["field"]
|
|
47
|
+
operator = rule["operator"]
|
|
48
|
+
value = rule["value"]
|
|
49
|
+
|
|
50
|
+
log_value = log_entry.get(field, "")
|
|
51
|
+
|
|
52
|
+
if operator == "equals":
|
|
53
|
+
return log_value == value
|
|
54
|
+
elif operator == "contains":
|
|
55
|
+
return value in str(log_value)
|
|
56
|
+
elif operator == "regex":
|
|
57
|
+
import re
|
|
58
|
+
|
|
59
|
+
return bool(re.search(value, str(log_value)))
|
|
60
|
+
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
def match_cases(self, log_entries, cases):
|
|
64
|
+
"""将日志条目与案例模板匹配"""
|
|
65
|
+
matched_cases = []
|
|
66
|
+
|
|
67
|
+
for log_entry in log_entries:
|
|
68
|
+
case = self.find_matching_case(log_entry, cases)
|
|
69
|
+
if case:
|
|
70
|
+
matched_case = self.enrich_case_with_log(case, log_entry)
|
|
71
|
+
matched_cases.append(matched_case)
|
|
72
|
+
|
|
73
|
+
return matched_cases
|
|
74
|
+
|
|
75
|
+
def find_matching_case(self, log_entry, cases):
|
|
76
|
+
"""根据规则找到匹配的案例模板"""
|
|
77
|
+
for case in cases:
|
|
78
|
+
if self.matches_rule(log_entry, case):
|
|
79
|
+
return case
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def matches_rule(self, log_entry, case_template):
|
|
83
|
+
"""检查日志是否匹配案例规则"""
|
|
84
|
+
rules = case_template.get("matching_rules", [])
|
|
85
|
+
|
|
86
|
+
for rule in rules:
|
|
87
|
+
if not self.check_single_rule(log_entry, rule):
|
|
88
|
+
return False
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
def enrich_case_with_log(self, case_template, log_entry):
|
|
92
|
+
"""用日志信息丰富案例内容"""
|
|
93
|
+
enriched_case = case_template.copy()
|
|
94
|
+
|
|
95
|
+
enriched_case["log_data"] = {
|
|
96
|
+
"timestamp": log_entry["timestamp"],
|
|
97
|
+
"parameters": log_entry["parameters"],
|
|
98
|
+
"raw_log": log_entry["raw_line"],
|
|
99
|
+
"line_number": log_entry["line_number"],
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# 替换模板中的占位符
|
|
103
|
+
if "description" in enriched_case:
|
|
104
|
+
enriched_case["description"] = self.fill_template(
|
|
105
|
+
enriched_case["description"], log_entry
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if "steps" in enriched_case:
|
|
109
|
+
enriched_case["steps"] = [
|
|
110
|
+
self.fill_template(step, log_entry)
|
|
111
|
+
for step in enriched_case["steps"]
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
return enriched_case
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
# Copyright (c) 2024 Huawei Technologies Co., Ltd.
|
|
4
|
+
# openUBMC is licensed under Mulan PSL v2.
|
|
5
|
+
# You can use this software according to the terms and conditions of the Mulan PSL v2.
|
|
6
|
+
# You may obtain a copy of Mulan PSL v2 at:
|
|
7
|
+
# http://license.coscl.org.cn/MulanPSL2
|
|
8
|
+
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
9
|
+
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
10
|
+
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
11
|
+
# See the Mulan PSL v2 for more details.
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LogParser:
|
|
17
|
+
@staticmethod
|
|
18
|
+
def extract_timestamp(log_line):
|
|
19
|
+
"""提取时间戳"""
|
|
20
|
+
import re
|
|
21
|
+
|
|
22
|
+
# 匹配常见的时间戳格式
|
|
23
|
+
patterns = [
|
|
24
|
+
r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}",
|
|
25
|
+
r"\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}",
|
|
26
|
+
]
|
|
27
|
+
for pattern in patterns:
|
|
28
|
+
match = re.search(pattern, log_line)
|
|
29
|
+
if match:
|
|
30
|
+
return match.group()
|
|
31
|
+
return "未知时间"
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def extract_log_level(log_line):
|
|
35
|
+
"""提取日志级别"""
|
|
36
|
+
|
|
37
|
+
levels = ["ERROR", "WARN", "WARNING", "INFO", "DEBUG", "FATAL", "Exception"]
|
|
38
|
+
for level in levels:
|
|
39
|
+
if level in log_line.upper():
|
|
40
|
+
return level
|
|
41
|
+
return "UNKNOWN"
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def extract_module(log_line):
|
|
45
|
+
"""提取模块名"""
|
|
46
|
+
import re
|
|
47
|
+
|
|
48
|
+
# 匹配 [ModuleName] 格式
|
|
49
|
+
pattern = r"\[([^\]]+)\]"
|
|
50
|
+
match = re.search(pattern, log_line)
|
|
51
|
+
return match.group(1) if match else "未知模块"
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def extract_message(log_line):
|
|
55
|
+
"""提取主要消息"""
|
|
56
|
+
return log_line.strip()
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def extract_error_code(log_line):
|
|
60
|
+
"""提取错误代码"""
|
|
61
|
+
import re
|
|
62
|
+
|
|
63
|
+
pattern = r"[A-Z]+_\d{3,}"
|
|
64
|
+
match = re.search(pattern, log_line)
|
|
65
|
+
return match.group() if match else "UNKNOWN"
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def extract_parameters(log_line):
|
|
69
|
+
"""提取日志中的参数"""
|
|
70
|
+
parameters = {}
|
|
71
|
+
import re
|
|
72
|
+
|
|
73
|
+
pattern = r"(\w+)=([^,\s]+)"
|
|
74
|
+
matches = re.findall(pattern, log_line)
|
|
75
|
+
for key, value in matches:
|
|
76
|
+
parameters[key] = value
|
|
77
|
+
return parameters
|
|
78
|
+
|
|
79
|
+
def parse_logs(self, file_path):
|
|
80
|
+
"""解析错误日志文件"""
|
|
81
|
+
log_entries = []
|
|
82
|
+
seen_logs = set() # 用于去重的集合
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
86
|
+
for line_num, line in enumerate(f, 1):
|
|
87
|
+
entry = self.parse_log_entry(line, line_num)
|
|
88
|
+
if entry:
|
|
89
|
+
# 去除颜色码并标准化日志内容
|
|
90
|
+
import re
|
|
91
|
+
|
|
92
|
+
ansi_escape = re.compile(
|
|
93
|
+
r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])"
|
|
94
|
+
)
|
|
95
|
+
clean_log = ansi_escape.sub("", entry["raw_line"]).strip()
|
|
96
|
+
|
|
97
|
+
# 检查是否已经处理过相同的日志
|
|
98
|
+
if clean_log not in seen_logs:
|
|
99
|
+
seen_logs.add(clean_log)
|
|
100
|
+
log_entries.append(entry)
|
|
101
|
+
except FileNotFoundError:
|
|
102
|
+
logging.warning(f"❌ 日志文件不存在: {file_path}")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logging.error(f"❌ 读取日志文件失败: {e}")
|
|
105
|
+
|
|
106
|
+
return log_entries
|
|
107
|
+
|
|
108
|
+
def parse_log_entry(self, log_line, line_num):
|
|
109
|
+
"""解析单条日志记录"""
|
|
110
|
+
try:
|
|
111
|
+
# 跳过空行
|
|
112
|
+
if not log_line.strip():
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
entry = {
|
|
116
|
+
"timestamp": self.extract_timestamp(log_line),
|
|
117
|
+
"level": self.extract_log_level(log_line),
|
|
118
|
+
"module": self.extract_module(log_line),
|
|
119
|
+
"message": self.extract_message(log_line),
|
|
120
|
+
"error_code": self.extract_error_code(log_line),
|
|
121
|
+
"parameters": self.extract_parameters(log_line),
|
|
122
|
+
"raw_line": log_line.strip(),
|
|
123
|
+
"line_number": line_num,
|
|
124
|
+
}
|
|
125
|
+
return entry
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logging.error(f"⚠️ 解析日志行 {line_num} 失败: {e}")
|
|
128
|
+
return None
|