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,809 @@
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
+ 解析BMC录制的日志文件,提取 bmc.* 服务的内容,包括方法请求和响应,信号
14
+ 并将其保存为 mock_data.json,用于 Mock 服务器。
15
+ """
16
+ import sys
17
+ import re
18
+ import os
19
+ import shutil
20
+ import logging
21
+ from bmcgo.errors import BmcGoException
22
+ from bmcgo.component.busctl_log_parse.mock_data_save import MockDataSaver
23
+ from bmcgo.component.busctl_log_parse.test_data_save import TestDataSave
24
+
25
+ # 配置日志记录
26
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
27
+ # --- 配置 ---#
28
+ DEFAULT_SERVICE_PREFIX = "bmc."
29
+
30
+
31
+ class BusCtlLogParser:
32
+ """
33
+ DBus日志解析器,用于解析dbus-monitor日志中的方法调用、返回和错误。
34
+ 该类封装了所有解析逻辑,并为未来的信号处理留下了扩展点。
35
+ """
36
+ # 在类的初始化方法中添加 signal 相关的状态变量
37
+ def __init__(self, test_service=None):
38
+ self.service_prefix = DEFAULT_SERVICE_PREFIX
39
+ # 保存mock数据,用于打桩
40
+ self.mock_data = {}
41
+ # 保存测试数据,用于生成用例
42
+ self.test_data = []
43
+ self.test_service = test_service
44
+ # 中间辅助变量
45
+ self.pending_calls = {}
46
+ self.current_req_rsp_obj = None
47
+ # Method Call 相关状态变量
48
+ self.current_call = None
49
+ self.current_args = []
50
+ self.current_arg_lines = []
51
+ self.in_arg_collection = False
52
+ self.bracket_balance = 0
53
+ self.expecting_empty_close = False # 标记是否期望空消息的结束
54
+ # Method Return 相关状态变量
55
+ self.current_return_values = None
56
+ self.current_return_lines = []
57
+ self.in_return_collection = False
58
+ self.return_bracket_balance = 0
59
+ # Signal 相关状态变量
60
+ self.signals = []
61
+ self.current_signal = None
62
+ self.current_signal_args = []
63
+ self.current_signal_lines = []
64
+ self.in_signal_collection = False
65
+ self.signal_bracket_balance = 0
66
+
67
+ @staticmethod
68
+ def process_signal_line(line):
69
+ """
70
+ 处理 signal 行,提取其中的信息
71
+
72
+ :param line: 日志中的 signal 行
73
+ :return: 解析后的 signal 信息字典
74
+ """
75
+ # 修改正则表达式,支持跨行匹配(允许换行和空格)
76
+ # 将多行合并为单行进行匹配
77
+ normalized_line = ' '.join(line.split())
78
+ type_pattern = (
79
+ r'Type=signal\s+Endian=\w\s+Flags=\d+\s+Version=\d+\s+'
80
+ r'Cookie=(\d+)\s+'
81
+ r'Timestamp="([^"]+)"\s+'
82
+ r'Sender=([^\s]+)'
83
+ r'(?:\s+Destination=[^\s]+)?\s+'
84
+ r'Path=([^\s]+)\s+'
85
+ r'Interface=([^\s]+)\s+'
86
+ r'Member=([^\s]+)'
87
+ )
88
+ call_match = re.search(type_pattern, normalized_line)
89
+ if not call_match:
90
+ return None
91
+ # 只提取需要的字段
92
+ cookie, timestamp, sender, path, iface, member = call_match.groups()
93
+ serial = int(cookie)
94
+ # 创建 signal 对象
95
+ signal_obj = {
96
+ 'type': 'signal',
97
+ 'timestamp': timestamp,
98
+ 'sender': sender,
99
+ 'path': path,
100
+ 'interface': iface,
101
+ 'member': member,
102
+ 'cookie': serial,
103
+ 'content': []
104
+ }
105
+ return signal_obj
106
+
107
+ @staticmethod
108
+ def needs_more_lines(line):
109
+ """
110
+ 检查一行参数是否需要更多行来完成
111
+ 通过括号平衡来判断是否需要继续收集
112
+
113
+ :param line: 当前行文本
114
+ :return: True 如果需要更多行,False 否则
115
+ """
116
+ # 计算括号平衡
117
+ bracket_balance = 0
118
+ for char in line:
119
+ if char in '{[(': # 开括号
120
+ bracket_balance += 1
121
+ elif char in '}])': # 闭括号
122
+ bracket_balance -= 1
123
+ # 如果括号不平衡,需要更多行
124
+ if bracket_balance != 0:
125
+ return True
126
+ array_contain = line.split('array [')[1]
127
+ dict_contain = line.split('dict entry(')[1]
128
+ flag_array_contain = 'array [' in line and ']' not in array_contain
129
+ flag_dict_contain = 'dict entry(' in line and ')' not in dict_contain
130
+ # 检查是否是复杂结构的开始但还没结束
131
+ if flag_array_contain or flag_dict_contain:
132
+ return True
133
+ return False
134
+
135
+ @staticmethod
136
+ def _process_indented_lines(stripped_line, current_lines, args_or_values, current_balance):
137
+ """
138
+ 通用处理缩进文本行的函数,统一处理method call的参数和method return的返回值
139
+
140
+ :param stripped_line: 去除缩进后的行文本
141
+ :param current_lines: 当前收集的行列表
142
+ :param args_or_values: 存储完成参数/返回值的列表
143
+ :param current_balance: 当前括号平衡状态
144
+ :return: 更新后的括号平衡状态
145
+ """
146
+ # 特殊处理XML字符串 - 检查是否已经在处理包含XML内容的参数
147
+ is_handling_xml = (current_lines and
148
+ any(line.startswith('string "<!DOCTYPE') or
149
+ line.startswith('STRING "<!DOCTYPE') for line in current_lines))
150
+ stripped_clean = stripped_line.strip()
151
+ # 当括号平衡为0且遇到独立的 "};" 时,这通常是 REQ_MESSAGE 的闭合标记,不应作为参数内容保存
152
+ if stripped_clean == '};' and current_balance == 0 and not is_handling_xml:
153
+ return current_balance
154
+ # 判断是否是新参数/返回值的开始:
155
+ # 1. 当前参数已经加了一些行
156
+ # 2. 当前括号平衡为0(表示上一个参数已经完整)
157
+ # 3. 当前行看起来像是新参数的开始(以类型关键字开头)
158
+ # 4. 不是XML内容
159
+ # 5. 当前行不是单纯的 };(这可能是 REQ_MESSAGE 或 RSP_MESSAGE 的结束)
160
+ is_new_param_start = (
161
+ stripped_line and
162
+ stripped_clean != '};' and # 排除单纯的 };,这可能是消息的结束
163
+ (stripped_line.startswith('ARRAY ') or
164
+ stripped_line.startswith('STRING ') or
165
+ stripped_line.startswith('INT32 ') or
166
+ stripped_line.startswith('INT64 ') or
167
+ stripped_line.startswith('UINT32 ') or
168
+ stripped_line.startswith('UINT64 ') or
169
+ stripped_line.startswith('DOUBLE ') or
170
+ stripped_line.startswith('BOOLEAN ') or
171
+ stripped_line.startswith('BYTE ') or
172
+ stripped_line.startswith('VARIANT ') or
173
+ stripped_line.startswith('DICT_ENTRY ') or
174
+ stripped_line.startswith('STRUCT ') or
175
+ stripped_line.startswith('OBJECT_PATH '))
176
+ )
177
+ flag_current_lines = current_lines and current_balance == 0
178
+ if flag_current_lines and is_new_param_start and not is_handling_xml:
179
+ # 如果是新参数/返回值且当前有未完成的,先保存
180
+ args_or_values.append('\n'.join(current_lines))
181
+ current_lines.clear()
182
+ current_lines.append(stripped_line)
183
+ # 更新括号平衡
184
+ new_balance = current_balance
185
+ for char in stripped_line:
186
+ if char in '{[(': # 开括号
187
+ new_balance += 1
188
+ elif char in '}])': # 闭括号
189
+ new_balance -= 1
190
+ return new_balance
191
+
192
+ @staticmethod
193
+ def _extract_pattern(text, pattern):
194
+ match = re.search(pattern, text)
195
+ return match.group(1) if match else None
196
+
197
+ @staticmethod
198
+ def _detect_block_type(block_lines):
199
+ """
200
+ 检测块类型:方法或信号
201
+ 优先检查 Type=signal,因为信号也可能包含 'method' 字符串
202
+ """
203
+ # 只检查前3行,因为 Type= 总是紧挨在 monitor_app: 的下一行
204
+ for line in block_lines[:3]:
205
+ if 'Type=signal' in line:
206
+ return 'signal'
207
+ if 'Type=method' in line:
208
+ return 'method'
209
+ return None
210
+
211
+ @staticmethod
212
+ def _collect_header_text(block_lines, terminators):
213
+ header_parts = []
214
+ for line in block_lines[1:]:
215
+ stripped = line.strip()
216
+ if any(stripped.startswith(term) for term in terminators):
217
+ break
218
+ if stripped:
219
+ header_parts.append(stripped)
220
+ return ' '.join(header_parts)
221
+
222
+ @staticmethod
223
+ def _collect_brace_block(lines, start_idx):
224
+ """
225
+ 收集从 start_idx 行开始的 { ... } 块,包含起始与结束行。
226
+ """
227
+ collected = []
228
+ brace_balance = 0
229
+ started = False
230
+ for idx in range(start_idx, len(lines)):
231
+ line = lines[idx]
232
+ collected.append(line)
233
+ delta = BusCtlLogParser._brace_delta(line)
234
+ brace_balance += delta
235
+ if ('{' in line) and not started:
236
+ started = True
237
+ if started and brace_balance == 0:
238
+ return collected, idx
239
+ return collected, len(lines) - 1
240
+
241
+ @staticmethod
242
+ def _split_top_level_entries(inner_lines):
243
+ entries = []
244
+ current = []
245
+ brace_balance = 0
246
+
247
+ for line in inner_lines:
248
+ if not line.strip() and not current:
249
+ continue
250
+ current.append(line)
251
+ brace_balance += BusCtlLogParser._brace_delta(line)
252
+ if brace_balance == 0:
253
+ entry = '\n'.join(current).strip()
254
+ if entry:
255
+ entries.append(entry)
256
+ current = []
257
+ if current:
258
+ entry = '\n'.join(current).strip()
259
+ if entry:
260
+ entries.append(entry)
261
+ return entries
262
+
263
+ @staticmethod
264
+ def _brace_delta(line):
265
+ delta = 0
266
+ in_quote = False
267
+ quote_char = ''
268
+ i = 0
269
+ while i < len(line):
270
+ ch = line[i]
271
+ if ch in ("'", '"'):
272
+ if not in_quote:
273
+ in_quote = True
274
+ quote_char = ch
275
+ elif quote_char == ch:
276
+ in_quote = False
277
+ quote_char = ''
278
+ i += 1
279
+ continue
280
+ if not in_quote:
281
+ if ch == '{':
282
+ delta += 1
283
+ elif ch == '}':
284
+ delta -= 1
285
+ i += 1
286
+ return delta
287
+
288
+ def parse_method_calls_and_responses(self, log_file_path):
289
+ """
290
+ 解析日志文件中的方法调用、响应与信号。
291
+ 采用块级解析方式:每个 monitor_app: 开头的块被视作一个独立的 Method 或 Signal。
292
+ """
293
+ self.mock_data = {}
294
+ self.signals = []
295
+ method_calls_count = 0
296
+ signals_count = 0
297
+ current_block = []
298
+ total_blocks = 0
299
+ with open(log_file_path, 'r', encoding='utf-8', errors='ignore') as f:
300
+ for raw_line in f:
301
+ line = raw_line.rstrip('\n')
302
+ if line.startswith('monitor_app:'):
303
+ if current_block:
304
+ if self._process_block(current_block):
305
+ block_type = self._detect_block_type(current_block)
306
+ if block_type == 'method':
307
+ method_calls_count += 1
308
+ elif block_type == 'signal':
309
+ signals_count += 1
310
+ total_blocks += 1
311
+ current_block = []
312
+ current_block.append(line)
313
+ else:
314
+ if current_block:
315
+ current_block.append(line)
316
+ # 处理最后一个块
317
+ if current_block:
318
+ if self._process_block(current_block):
319
+ block_type = self._detect_block_type(current_block)
320
+ if block_type == 'method':
321
+ method_calls_count += 1
322
+ elif block_type == 'signal':
323
+ signals_count += 1
324
+ total_blocks += 1
325
+ logging.info(
326
+ f"✅ 解析完成: 块数={total_blocks} | 方法={method_calls_count} | 信号={signals_count} | "
327
+ f"Mock条目={sum(len(v) for v in self.mock_data.values())}"
328
+ )
329
+ return self.mock_data
330
+
331
+ def parse_dbus_log(self, log_file_path, output_dir_path):
332
+ """
333
+ 解析标准 dbus-monitor 日志文件并生成 Mock 数据。
334
+
335
+ :param log_file_path: dbus-monitor 日志文件路径
336
+ :param output_dir_path: 输出目录路径
337
+ """
338
+ try:
339
+ # 创建输出目录(如果不存在)
340
+ os.makedirs(output_dir_path, exist_ok=True)
341
+ # 清理 mock_data 和 test_data 目录(如果存在)
342
+ mock_data_dir = os.path.join(output_dir_path, 'mock_data')
343
+ test_data_dir = os.path.join(output_dir_path, 'test_data')
344
+ if os.path.exists(mock_data_dir):
345
+ shutil.rmtree(mock_data_dir)
346
+ if os.path.exists(test_data_dir):
347
+ shutil.rmtree(test_data_dir)
348
+ # 获取目录中所有文件并排序
349
+ log_files = []
350
+ for root, _, files in os.walk(log_file_path):
351
+ for file in files:
352
+ if file.endswith('.log'): # 只处理.log文件
353
+ log_files.append(os.path.join(root, file))
354
+ # 按文件名排序,确保按顺序处理
355
+ log_files.sort()
356
+ if not log_files:
357
+ logging.warning(f"警告: 目录 {log_file_path} 中没有找到.log文件")
358
+ return
359
+ logging.info(f" 找到 {len(log_files)} 个日志文件")
360
+ # 解析方法调用和响应
361
+ logging.info("解析方法调用和响应...")
362
+ # 重新创建目录(之前已清理)
363
+ os.makedirs(test_data_dir, exist_ok=True)
364
+ os.makedirs(mock_data_dir, exist_ok=True)
365
+ for log_file in log_files:
366
+ logging.info(f"处理文件: {log_file}")
367
+ # 解析当前文件
368
+ self.parse_method_calls_and_responses(log_file)
369
+ if self.mock_data:
370
+ # 保存mock数据和signal数据,由于数据量可能比较大,分开批量处理
371
+ mock_data_saver = MockDataSaver(self.service_prefix)
372
+ mock_data_saver.save_mock_data(self.mock_data, mock_data_dir)
373
+ # 保存测试数据,一次性保存
374
+ if self.test_data:
375
+ test_data_saver = TestDataSave(self.test_data, output_dir_path)
376
+ test_data_saver.save()
377
+ except FileNotFoundError:
378
+ raise BmcGoException(f"错误: 找不到日志文件 {log_file_path}") from e
379
+ except Exception as e:
380
+ raise BmcGoException(f"解析日志时出错: {e}") from e
381
+
382
+ def process_method_call(self, line):
383
+ """
384
+ 处理方法调用行,提取调用信息。
385
+
386
+ :param line: 日志中的方法调用行
387
+ :return: 包含调用信息的字典,如果不是目标服务则返回None
388
+ """
389
+ type_pattern = (
390
+ r'Type=method\s+'
391
+ r'Endian=\w\s+'
392
+ r'Flags=\d+\s+'
393
+ r'Version=\d+\s+'
394
+ r'Cookie=(\d+)\s+'
395
+ r'Timestamp="([^"]+)"\s+'
396
+ r'Sender=([^\s]+)\s+'
397
+ r'Destination=([^\s]+)\s+'
398
+ r'Path=([^\s]+)\s+'
399
+ r'Interface=([^\s]+)\s+'
400
+ r'Member=([^\s]+)'
401
+ )
402
+ call_match = re.search(type_pattern, line)
403
+ if not call_match:
404
+ return None
405
+ cookie, timestamp, sender, dest, path, iface, member = call_match.groups()
406
+ serial = int(cookie)
407
+ # 检查 destination 是否是需要关注的服务
408
+ is_target_service = False
409
+ if dest.startswith(self.service_prefix):
410
+ is_target_service = True
411
+ if not is_target_service:
412
+ # 不是我们关心的服务调用,跳过
413
+ return None
414
+ return {
415
+ "type": "method_call",
416
+ "timestamp": timestamp,
417
+ "sender": sender,
418
+ "destination": dest,
419
+ "path": path,
420
+ "interface": iface,
421
+ "member": member,
422
+ "cookie": serial,
423
+ "args": []
424
+ }
425
+
426
+ def process_method_return(self, line):
427
+ """
428
+ 处理方法返回行,将返回与调用匹配。
429
+
430
+ :param line: 日志中的方法返回行
431
+ :return: 响应值收集状态,如果需要收集响应值则返回(reply_serial, response_obj),否则返回None
432
+ """
433
+ # 提取signatural到RESP_MESSAGE=之间的内容
434
+ sig_match = re.search(r'RSP_MESSAGE\s+"([^"]*)"', line)
435
+ signature = '' # 初始化为空字符串
436
+ if sig_match:
437
+ signature = sig_match.group(1)
438
+ original_call = self.current_call
439
+ response_obj = {
440
+ "request": original_call,
441
+ "response": {
442
+ "type": "method_return",
443
+ "signature": signature,
444
+ "values": [] # 用于存储响应值
445
+ }
446
+ }
447
+ # 不立即添加到mock_data,等待收集响应值
448
+ return response_obj
449
+
450
+ def process_remaining_calls(self):
451
+ """
452
+ 处理未匹配到返回的调用,视为超时或无响应。
453
+
454
+ :return: None
455
+ """
456
+ for call_data in self.pending_calls.values():
457
+ key_dest = call_data['destination']
458
+ key = f"{key_dest}|{call_data['path']}|{call_data['interface']}|{call_data['member']}"
459
+ if key not in self.mock_data:
460
+ self.mock_data[key] = []
461
+ self.mock_data[key].append({
462
+ "request": call_data,
463
+ "response": {"type": "no_reply"}
464
+ })
465
+
466
+ def _handle_signal_line(self, line):
467
+ """
468
+ 处理 signal 行
469
+
470
+ :param line: 日志中的 signal 行
471
+ :return: signal 对象,如果解析成功则返回,否则返回 None
472
+ """
473
+ # 如果之前有未完成的 signal,先保存
474
+ if self.current_signal:
475
+ self._finalize_signal_collection()
476
+ self.current_signal = BusCtlLogParser.process_signal_line(line)
477
+ signal_result = self.current_signal # 保存返回值用于统计
478
+ if self.current_signal:
479
+ self.current_signal_args = []
480
+ self.current_signal_lines = []
481
+ self.in_signal_collection = True
482
+ self.signal_bracket_balance = 0
483
+ else:
484
+ self.current_signal = None
485
+ self.in_signal_collection = False
486
+ return signal_result # 返回 signal 对象用于统计
487
+
488
+ def _process_signal_arg_lines(self, stripped_line):
489
+ """
490
+ 处理 signal 参数的后续行
491
+
492
+ :param stripped_line: 去除缩进后的行文本
493
+ """
494
+ # 使用与调用参数相同的处理逻辑来解析缩进的文本行
495
+ new_balance = BusCtlLogParser._process_indented_lines(
496
+ stripped_line,
497
+ self.current_signal_lines,
498
+ self.current_signal_args,
499
+ self.signal_bracket_balance
500
+ )
501
+ # 更新类中的括号平衡状态
502
+ self.signal_bracket_balance = new_balance
503
+
504
+ def _finalize_signal_collection(self):
505
+ """
506
+ 当遇到非缩进行时,如果有未完成的参数收集,则完成 signal 收集
507
+ 只添加以service_prefix开头的信号
508
+ """
509
+ if not self.current_signal:
510
+ return
511
+ # 保存最后一个参数
512
+ if self.current_signal_lines:
513
+ self.current_signal_args.append('\n'.join(self.current_signal_lines))
514
+ # 将收集的参数赋值给 content(保留完整内容,不删除任何结尾)
515
+ self.current_signal['content'] = self.current_signal_args
516
+ self.signals.append(self.current_signal)
517
+ self.test_data.append(self.current_signal)
518
+ # 重置状态 - 使用函数调用替代直接重置
519
+ self._reset_signal_state()
520
+
521
+ def _reset_signal_state(self):
522
+ """
523
+ 重置signal相关的状态变量
524
+ """
525
+ self.current_signal = None
526
+ self.current_signal_args = []
527
+ self.current_signal_lines = []
528
+ self.in_signal_collection = False
529
+ self.signal_bracket_balance = 0
530
+
531
+ def _process_block(self, block_lines):
532
+ if not block_lines:
533
+ return False
534
+ block_type = self._detect_block_type(block_lines)
535
+ if block_type == 'method':
536
+ response_obj = self._process_method_block(block_lines)
537
+ if response_obj:
538
+ self._save_req_rsp_obj(response_obj)
539
+ return True
540
+ elif block_type == 'signal':
541
+ signal_obj = self._process_signal_block(block_lines)
542
+ if signal_obj:
543
+ self.signals.append(signal_obj)
544
+ self.test_data.append(signal_obj)
545
+ return True
546
+ return False
547
+
548
+ def _process_method_block(self, block_lines):
549
+ """
550
+ 处理方法块:提取方法调用的请求和响应
551
+ 注意:信号不应该有 REQ_MESSAGE 和 RSP_MESSAGE,只有 MESSAGE
552
+ """
553
+ # 再次确认这是方法块,不是信号块
554
+ block_type = self._detect_block_type(block_lines)
555
+ if block_type != 'method':
556
+ return None
557
+ header_text = self._collect_header_text(block_lines, terminators=('REQ_MESSAGE',))
558
+ call_info = self._parse_method_header(header_text)
559
+ if not call_info:
560
+ return None
561
+ req_signature, req_entries = self._extract_section_entries(block_lines, 'REQ_MESSAGE')
562
+ rsp_signature, rsp_entries = self._extract_section_entries(block_lines, 'RSP_MESSAGE')
563
+ call_info['signature'] = req_signature or ''
564
+ call_info['args'] = req_entries
565
+ response_obj = {
566
+ 'request': call_info,
567
+ 'response': {
568
+ 'type': 'method_return',
569
+ 'signature': rsp_signature or '',
570
+ 'values': rsp_entries
571
+ }
572
+ }
573
+ return response_obj
574
+
575
+ def _process_signal_block(self, block_lines):
576
+ """
577
+ 处理信号块:提取信号的内容
578
+ 注意:信号只有 MESSAGE,没有 REQ_MESSAGE 和 RSP_MESSAGE
579
+ """
580
+ # 再次确认这是信号块,不是方法块
581
+ block_type = self._detect_block_type(block_lines)
582
+ if block_type != 'signal':
583
+ return None
584
+ header_text = self._collect_header_text(block_lines, terminators=('MESSAGE',))
585
+ signal_info = self._parse_signal_header(header_text)
586
+ if not signal_info:
587
+ return None
588
+ message_signature, message_entries = self._extract_section_entries(block_lines, 'MESSAGE')
589
+ signal_info['signature'] = message_signature or ''
590
+ signal_info['content'] = message_entries
591
+ return signal_info
592
+
593
+ def _parse_method_header(self, header_text):
594
+ if not header_text:
595
+ return None
596
+ data = {
597
+ 'type': 'method_call',
598
+ 'timestamp': self._extract_pattern(header_text, r'Timestamp="([^"]+)"') or '',
599
+ 'sender': self._extract_pattern(header_text, r'Sender=([^\s]+)') or '',
600
+ 'destination': self._extract_pattern(header_text, r'Destination=([^\s]+)') or '',
601
+ 'path': self._extract_pattern(header_text, r'Path=([^\s]+)') or '',
602
+ 'interface': self._extract_pattern(header_text, r'Interface=([^\s]+)') or '',
603
+ 'member': self._extract_pattern(header_text, r'Member=([^\s]+)') or '',
604
+ 'cookie': int(self._extract_pattern(header_text, r'Cookie=(\d+)') or 0),
605
+ 'args': []
606
+ }
607
+ if not data['destination'].startswith(self.service_prefix):
608
+ return None
609
+ return data
610
+
611
+ def _parse_signal_header(self, header_text):
612
+ if not header_text:
613
+ return None
614
+ return {
615
+ 'type': 'signal',
616
+ 'timestamp': self._extract_pattern(header_text, r'Timestamp="([^"]+)"') or '',
617
+ 'sender': self._extract_pattern(header_text, r'Sender=([^\s]+)') or '',
618
+ 'path': self._extract_pattern(header_text, r'Path=([^\s]+)') or '',
619
+ 'interface': self._extract_pattern(header_text, r'Interface=([^\s]+)') or '',
620
+ 'member': self._extract_pattern(header_text, r'Member=([^\s]+)') or '',
621
+ 'cookie': int(self._extract_pattern(header_text, r'Cookie=(\d+)') or 0),
622
+ }
623
+
624
+ def _extract_section_entries(self, block_lines, section_keyword):
625
+ """
626
+ 提取 REQ_MESSAGE / RSP_MESSAGE / MESSAGE 内容,并拆分为顶层条目列表。
627
+ """
628
+ signature = ''
629
+ start_idx = None
630
+ for idx, line in enumerate(block_lines):
631
+ stripped = line.strip()
632
+ if stripped.startswith(section_keyword):
633
+ start_idx = idx
634
+ signature = self._extract_pattern(stripped, fr'{section_keyword}\s+"([^"]*)"') or ''
635
+ break
636
+ if start_idx is None:
637
+ return signature, []
638
+ section_lines, end_idx = self._collect_brace_block(block_lines, start_idx)
639
+ if not section_lines:
640
+ return signature, []
641
+ # 去掉起始行和最终的包裹行,仅保留内部内容
642
+ inner_lines = section_lines[1:-1] if len(section_lines) >= 2 else []
643
+ entries = self._split_top_level_entries(inner_lines)
644
+ return signature, entries
645
+
646
+ def _reset_return_state(self):
647
+ """
648
+ 重置method return相关的状态变量
649
+ """
650
+ self.current_call = None
651
+ self.current_return_values = None
652
+ self.current_return_lines = []
653
+ self.in_return_collection = False
654
+ self.return_bracket_balance = 0
655
+ self.current_req_rsp_obj = None
656
+
657
+ def _process_return_value_lines(self, stripped_line):
658
+ """
659
+ 处理响应值的后续行
660
+
661
+ :param stripped_line: 去除缩进后的行文本
662
+ """
663
+ stripped_clean = stripped_line.strip()
664
+ # 如果当前返回体已经结束(遇到RSP_MESSAGE的闭合标记),提前完成收集
665
+ if stripped_clean == '};' and self.return_bracket_balance == 0:
666
+ if self.in_return_collection:
667
+ self._finalize_return_collection()
668
+ return
669
+
670
+ # 直接传递当前的括号平衡状态,接收返回的结果和新的平衡状态
671
+ new_balance = BusCtlLogParser._process_indented_lines(
672
+ stripped_line,
673
+ self.current_return_lines,
674
+ self.current_return_values,
675
+ self.return_bracket_balance
676
+ )
677
+ # 更新类中的括号平衡状态
678
+ self.return_bracket_balance = new_balance
679
+
680
+ def _finalize_return_collection(self):
681
+ """
682
+ 完成DBus方法返回值的收集和处理,将完整的请求-响应对保存到mock_data中。
683
+ """
684
+ # 处理可能剩余的返回值
685
+ if self.current_return_lines:
686
+ self.current_return_values.append('\n'.join(self.current_return_lines))
687
+ self.current_return_lines = []
688
+ response_obj = self.current_req_rsp_obj
689
+ if response_obj:
690
+ self._save_req_rsp_obj(response_obj)
691
+ self._reset_return_state()
692
+
693
+ def _save_req_rsp_obj(self, response_obj):
694
+ """
695
+ 保存请求-响应对象到mock_data和test_data
696
+
697
+ Args:
698
+ response_obj: 请求-响应对象
699
+ """
700
+ if not response_obj:
701
+ return
702
+ key_dest = response_obj['request']['destination']
703
+ key = (
704
+ f"{key_dest}|{response_obj['request']['path']}|"
705
+ f"{response_obj['request']['interface']}|"
706
+ f"{response_obj['request']['member']}"
707
+ )
708
+ should_save_test_data = self.test_service and self.test_service == key_dest
709
+ should_save_mock_data = not self.test_service or self.test_service != key_dest
710
+ if should_save_mock_data:
711
+ if key not in self.mock_data:
712
+ self.mock_data[key] = []
713
+ self.mock_data[key].append(response_obj)
714
+
715
+ if should_save_test_data:
716
+ self.test_data.append(response_obj)
717
+
718
+ def _handle_method_return_line(self, line):
719
+ """
720
+ 处理method return行
721
+
722
+ :param line: 日志中的method return行
723
+ """
724
+ response_obj = self.process_method_return(line)
725
+ if response_obj:
726
+ self.current_req_rsp_obj = response_obj
727
+ self.current_return_values = response_obj['response']['values']
728
+ self.in_return_collection = True
729
+ self.current_return_lines = []
730
+ self.return_bracket_balance = 0
731
+
732
+ def _reset_call_state(self):
733
+ """
734
+ 重置method call相关的状态变量
735
+ """
736
+ self.current_args = []
737
+ self.current_arg_lines = []
738
+ self.in_arg_collection = False
739
+ self.bracket_balance = 0
740
+ self.expecting_empty_close = False
741
+
742
+ def _process_call_arg_lines(self, stripped_line):
743
+ """
744
+ 处理调用参数的后续行
745
+
746
+ :param stripped_line: 去除缩进后的行文本
747
+ """
748
+ # 直接传递当前的括号平衡状态,接收返回的结果和新的平衡状态
749
+ new_balance = BusCtlLogParser._process_indented_lines(
750
+ stripped_line,
751
+ self.current_arg_lines,
752
+ self.current_args,
753
+ self.bracket_balance
754
+ )
755
+ # 更新类中的括号平衡状态
756
+ self.bracket_balance = new_balance
757
+
758
+ def _finalize_call_collection(self):
759
+ """
760
+ 当遇到非缩进行时,如果有未完成的参数收集,则完成调用
761
+ 确保所有参数行(包括最后的 };)都被完整保存
762
+ """
763
+ # 保存最后一个参数(包括所有行,不删除任何内容)
764
+ if self.current_arg_lines:
765
+ # 确保所有行都被保存,包括可能的空行和结尾的 };
766
+ param_text = '\n'.join(self.current_arg_lines)
767
+ if param_text.strip(): # 只有当参数不为空时才添加
768
+ self.current_args.append(param_text)
769
+ self.current_call["args"] = self.current_args
770
+ self._reset_call_state()
771
+
772
+ def _handle_method_call_line(self, line):
773
+ """
774
+ 处理method call行
775
+
776
+ :param line: 日志中的method call行
777
+ """
778
+ # 如果上一个 call 还没结束,则保存
779
+ if self.current_call:
780
+ if self.in_arg_collection and self.current_arg_lines:
781
+ # 保存最后一个参数
782
+ self.current_args.append('\n'.join(self.current_arg_lines))
783
+ self.current_arg_lines = []
784
+ self.current_call["args"] = self.current_args
785
+ # 如果上一个调用没有响应,创建一个空的响应对象
786
+ if self.in_arg_collection or not self.current_req_rsp_obj:
787
+ # 创建一个只有请求没有响应的对象(可能日志中没有响应)
788
+ response_obj = {
789
+ 'request': self.current_call.copy(),
790
+ 'response': {
791
+ 'type': 'method_return',
792
+ 'signature': self.current_call.get('signature', ''),
793
+ 'values': []
794
+ }
795
+ }
796
+ self._save_req_rsp_obj(response_obj)
797
+ # 重置状态
798
+ self._reset_call_state()
799
+ self.current_call = self.process_method_call(line)
800
+ call_result = self.current_call # 保存返回值用于统计
801
+ if self.current_call:
802
+ self.current_args = []
803
+ self.current_arg_lines = []
804
+ self.in_arg_collection = True
805
+ self.bracket_balance = 0
806
+ else:
807
+ self.current_call = None
808
+ self.in_arg_collection = False
809
+ return call_result # 返回 call 对象用于统计