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,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 对象用于统计
|