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.
Files changed (96) hide show
  1. bmcgo/__init__.py +1 -1
  2. bmcgo/bmcgo.py +9 -3
  3. bmcgo/bmcgo_config.py +16 -0
  4. bmcgo/cli/cli.py +72 -21
  5. bmcgo/codegen/__init__.py +1 -1
  6. bmcgo/codegen/lua/codegen.py +2 -2
  7. bmcgo/codegen/lua/script/check_intfs.py +1 -0
  8. bmcgo/codegen/lua/script/dto/options.py +1 -0
  9. bmcgo/codegen/lua/script/gen_db_json.py +4 -3
  10. bmcgo/codegen/lua/script/gen_rpc_msg_json.py +78 -11
  11. bmcgo/codegen/lua/script/model_consistency_check.py +1 -1
  12. bmcgo/codegen/lua/script/render_utils/db_lua.py +5 -6
  13. bmcgo/codegen/lua/script/render_utils/model_lua.py +5 -1
  14. bmcgo/codegen/lua/script/template.py +5 -0
  15. bmcgo/codegen/lua/script/utils.py +50 -8
  16. bmcgo/codegen/lua/templates/apps/Makefile +2 -2
  17. bmcgo/codegen/lua/templates/apps/client.lua.mako +1 -1
  18. bmcgo/codegen/lua/templates/apps/model.lua.mako +4 -3
  19. bmcgo/codegen/lua/templates/apps/service.lua.mako +1 -1
  20. bmcgo/codegen/lua/templates/apps/utils/mdb_intf.lua.mako +4 -0
  21. bmcgo/codegen/lua/templates/new_app_v2/CMakeLists.txt.mako +26 -0
  22. bmcgo/codegen/lua/templates/new_app_v2/conanfile.py.mako +9 -0
  23. bmcgo/codegen/lua/v1/script/render_utils/db_lua.py +5 -6
  24. bmcgo/codegen/lua/v1/script/render_utils/model_lua.py +13 -1
  25. bmcgo/codegen/lua/v1/templates/apps/client.lua.mako +1 -1
  26. bmcgo/codegen/lua/v1/templates/apps/local_db.lua.mako +0 -4
  27. bmcgo/codegen/lua/v1/templates/apps/message.lua.mako +3 -0
  28. bmcgo/codegen/lua/v1/templates/apps/model.lua.mako +3 -0
  29. bmcgo/codegen/lua/v1/templates/apps/utils/mdb_intf.lua.mako +6 -4
  30. bmcgo/component/analysis/analysis.py +9 -4
  31. bmcgo/component/analysis/dep-rules.json +20 -8
  32. bmcgo/component/analysis/dep_node.py +2 -0
  33. bmcgo/component/analysis/intf_validation.py +8 -7
  34. bmcgo/component/analysis/sr_validation.py +5 -4
  35. bmcgo/component/busctl_log_parse/busctl_log_parser.py +809 -0
  36. bmcgo/component/busctl_log_parse/mock_data_save.py +170 -0
  37. bmcgo/component/busctl_log_parse/test_data_save.py +49 -0
  38. bmcgo/component/component_helper.py +29 -0
  39. bmcgo/component/coverage/incremental_cov.py +5 -0
  40. bmcgo/component/fixture/__init__.py +29 -0
  41. bmcgo/component/fixture/auto_case_generator.py +490 -0
  42. bmcgo/component/fixture/busctl_type_converter.py +1081 -0
  43. bmcgo/component/fixture/common_config.py +15 -0
  44. bmcgo/component/fixture/dbus_gateway.py +669 -0
  45. bmcgo/component/fixture/dbus_library.py +250 -0
  46. bmcgo/component/fixture/dbus_mock_utils.py +514 -0
  47. bmcgo/component/fixture/dbus_response_handler.py +138 -0
  48. bmcgo/component/fixture/dbus_signature.py +110 -0
  49. bmcgo/component/template_v2/conanbase.py.mako +1 -5
  50. bmcgo/component/test.py +69 -10
  51. bmcgo/error_analyzer/__init__.py +0 -0
  52. bmcgo/error_analyzer/case_matcher.py +114 -0
  53. bmcgo/error_analyzer/log_parser.py +128 -0
  54. bmcgo/error_analyzer/unified_error_analyzer.py +359 -0
  55. bmcgo/error_cases/cases.yml +59 -0
  56. bmcgo/error_cases/cases_template_valid.json +71 -0
  57. bmcgo/error_cases/conanfile.py +58 -0
  58. bmcgo/frame.py +0 -4
  59. bmcgo/functional/analysis.py +18 -12
  60. bmcgo/functional/bmc_studio_action.py +21 -10
  61. bmcgo/functional/check.py +86 -42
  62. bmcgo/functional/conan_index_build.py +1 -1
  63. bmcgo/functional/config.py +22 -18
  64. bmcgo/functional/csr_build.py +63 -34
  65. bmcgo/functional/deploy.py +4 -3
  66. bmcgo/functional/diff.py +51 -34
  67. bmcgo/functional/full_component.py +16 -5
  68. bmcgo/functional/hpm_signer.py +484 -0
  69. bmcgo/functional/new.py +8 -2
  70. bmcgo/functional/schema_valid.py +111 -15
  71. bmcgo/functional/upgrade.py +6 -6
  72. bmcgo/misc.py +1 -0
  73. bmcgo/tasks/task_build_conan.py +27 -6
  74. bmcgo/tasks/task_build_rootfs_img.py +120 -83
  75. bmcgo/tasks/task_buildgppbin.py +30 -13
  76. bmcgo/tasks/task_buildhpm_ext4.py +5 -3
  77. bmcgo/tasks/task_download_buildtools.py +20 -11
  78. bmcgo/tasks/task_download_dependency.py +29 -20
  79. bmcgo/tasks/task_hpm_envir_prepare.py +32 -53
  80. bmcgo/tasks/task_packet_to_supporte.py +12 -4
  81. bmcgo/tasks/task_prepare.py +1 -1
  82. bmcgo/tasks/task_sign_and_pack_hpm.py +15 -7
  83. bmcgo/utils/component_version_check.py +4 -4
  84. bmcgo/utils/config.py +3 -0
  85. bmcgo/utils/fetch_component_code.py +148 -17
  86. bmcgo/utils/install_manager.py +2 -2
  87. bmcgo/utils/installations/base_installer.py +10 -27
  88. bmcgo/utils/installations/install_plans/studio.yml +3 -0
  89. bmcgo/utils/mapping_config_patch.py +5 -4
  90. bmcgo/utils/tools.py +49 -7
  91. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/METADATA +1 -1
  92. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/RECORD +95 -74
  93. bmcgo/tasks/download_buildtools_hm.py +0 -124
  94. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/WHEEL +0 -0
  95. {openubmc_bingo-0.6.45.dist-info → openubmc_bingo-0.6.99.dist-info}/entry_points.txt +0 -0
  96. {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
- test_folder = os.path.join(self.folder, "test")
880
- if os.path.isdir(test_folder) or os.path.isdir(test_package_folder):
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("当前组件不存在开发者测试的test文件夹或test_package文件夹,开发者测试终止!")
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