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,514 @@
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
+ # 自动生成的DBUS打桩服务公共工具类
13
+ import json
14
+ import os
15
+ import logging
16
+ from bmcgo.component.fixture.dbus_response_handler import DBusResponseHandler
17
+ from bmcgo.component.fixture.common_config import CommonConfig
18
+ # 首先需要导入 DBusTypeConverter
19
+ from bmcgo.component.fixture.busctl_type_converter import BusCtlTypeConverter
20
+
21
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
22
+ MOCK_CONTROL_SERVICE = 'bmc.kepler.MockControl'
23
+ MOCK_CONTROL_OBJECT_PATH = '/bmc/kepler/MockControl'
24
+ MOCK_CONTROL_INTERFACE = 'bmc.kepler.MockControl'
25
+ # 全局运行时 mock 数据存储(跨服务共享)
26
+ # 格式:{service_name: {lookup_key: [records]}}
27
+ _runtime_mock_data = {}
28
+
29
+
30
+ def set_runtime_mock(service_name, lookup_key, response, args=None, match_mode='exact'):
31
+ """在运行时设置 mock 响应
32
+
33
+ Args:
34
+ service_name: 服务名称,如 'bmc.kepler.persistence'
35
+ lookup_key: 方法键,格式:'service|path|interface|method' 或完整格式
36
+ response: 响应数据,支持以下格式:
37
+ - dict: 包含 'type' 和响应数据的字典
38
+ - type='method_return': 正常响应,需要 'values' 和 'signature'
39
+ - type='error': 错误响应,需要 'error_name' 和 'error_message'
40
+ - type='timeout'/'no_reply': 超时响应
41
+ - type='delay': 延迟响应,需要 'delay_seconds' 或 'delay_ms' 和响应数据
42
+ - type='interrupt': 中断响应,需要 'error_name' 和 'error_message'
43
+ - list: 响应值列表(自动转换为 method_return)
44
+ - 其他: 单个响应值(自动转换为 method_return)
45
+ args: 请求参数列表(可选),用于参数匹配。如果为 None,匹配所有参数
46
+ match_mode: 匹配模式
47
+ - 'exact': 精确匹配参数(默认)
48
+ - 'any': 匹配任意参数
49
+
50
+ Returns:
51
+ None
52
+ """
53
+ if service_name not in _runtime_mock_data:
54
+ _runtime_mock_data[service_name] = {}
55
+
56
+ if lookup_key not in _runtime_mock_data[service_name]:
57
+ _runtime_mock_data[service_name][lookup_key] = []
58
+
59
+ # 构建响应记录
60
+ if isinstance(response, dict):
61
+ # 已经是完整的响应格式
62
+ response_dict = response.copy()
63
+ if 'type' not in response_dict:
64
+ response_dict['type'] = 'method_return'
65
+ elif isinstance(response, list):
66
+ # 列表格式,转换为 method_return
67
+ response_dict = {
68
+ 'type': 'method_return',
69
+ 'values': response,
70
+ 'signature': ''
71
+ }
72
+ else:
73
+ # 单个值,转换为 method_return
74
+ response_dict = {
75
+ 'type': 'method_return',
76
+ 'values': [response],
77
+ 'signature': ''
78
+ }
79
+
80
+ # 构建请求记录
81
+ request_dict = {
82
+ 'args': args if args is not None else []
83
+ }
84
+
85
+ record = {
86
+ 'request': request_dict,
87
+ 'response': response_dict,
88
+ '_runtime_mock': True, # 标记为运行时 mock
89
+ '_match_mode': match_mode
90
+ }
91
+
92
+ _runtime_mock_data[service_name][lookup_key].append(record)
93
+ logging.info(f'✅ 已设置运行时 mock: {service_name} {lookup_key} (参数: {args}, 模式: {match_mode})')
94
+
95
+
96
+ def clear_runtime_mock(service_name=None, lookup_key=None):
97
+ """清除运行时 mock 数据
98
+
99
+ Args:
100
+ service_name: 服务名称,如果为 None 则清除所有服务的 mock
101
+ lookup_key: 方法键,如果为 None 则清除该服务的所有 mock
102
+
103
+ Returns:
104
+ None
105
+ """
106
+ if service_name is None:
107
+ _runtime_mock_data.clear()
108
+ logging.info('✅ 已清除所有运行时 mock 数据')
109
+ elif lookup_key is None:
110
+ if service_name in _runtime_mock_data:
111
+ del _runtime_mock_data[service_name]
112
+ logging.info(f'✅ 已清除服务 {service_name} 的所有运行时 mock 数据')
113
+ else:
114
+ if service_name in _runtime_mock_data and lookup_key in _runtime_mock_data[service_name]:
115
+ del _runtime_mock_data[service_name][lookup_key]
116
+ logging.info(f'✅ 已清除运行时 mock: {service_name} {lookup_key}')
117
+
118
+
119
+ def get_runtime_mock_data(service_name):
120
+ """获取指定服务的运行时 mock 数据
121
+
122
+ Args:
123
+ service_name: 服务名称
124
+
125
+ Returns:
126
+ dict: 运行时 mock 数据,格式:{lookup_key: [records]}
127
+ """
128
+ return _runtime_mock_data.get(service_name, {})
129
+
130
+
131
+ # 公共接口函数(供用例代码使用)
132
+ def set_mock_response(service_name, lookup_key, response, args=None, match_mode='exact'):
133
+ """在运行时设置 mock 响应(公共接口)
134
+
135
+ 这是 set_runtime_mock 的公共接口,供用例代码使用。
136
+ 优先级高于 mock_data.json 中的配置。
137
+
138
+ 注意:由于 dbus_gateway.py 运行在独立进程中,通过 D-Bus 接口调用。
139
+
140
+ Args:
141
+ service_name: 服务名称,如 'bmc.kepler.persistence'
142
+ lookup_key: 方法键,格式:'service|path|interface|method'
143
+ response: 响应数据,支持以下格式:
144
+ - dict: 包含 'type' 和响应数据的字典
145
+ - type='method_return': 正常响应,需要 'values' 和 'signature'
146
+ - type='error': 错误响应,需要 'error_name' 和 'error_message'
147
+ - type='timeout'/'no_reply': 超时响应
148
+ - type='delay': 延迟响应,需要 'delay_seconds' 或 'delay_ms' 和响应数据
149
+ - type='interrupt': 中断响应,需要 'error_name' 和 'error_message'
150
+ - list: 响应值列表(自动转换为 method_return)
151
+ - 其他: 单个响应值(自动转换为 method_return)
152
+ args: 请求参数列表(可选),用于参数匹配。如果为 None,匹配所有参数
153
+ match_mode: 匹配模式 'exact'(精确匹配)或 'any'(匹配任意参数)
154
+
155
+ 示例:
156
+ # 设置正常响应
157
+ set_mock_response(
158
+ 'bmc.kepler.persistence',
159
+ 'bmc.kepler.persistence|/bmc/kepler/persistence|bmc.kepler.persistence|BatchRead',
160
+ {'type': 'method_return', 'values': ['STRING "success"'], 'signature': 's'}
161
+ )
162
+
163
+ # 设置错误响应
164
+ set_mock_response(
165
+ 'bmc.kepler.persistence',
166
+ 'bmc.kepler.persistence|/bmc/kepler/persistence|bmc.kepler.persistence|BatchRead',
167
+ {'type': 'error', 'error_name': 'org.freedesktop.DBus.Error.Failed', 'error_message': 'Mock error'}
168
+ )
169
+
170
+ # 设置超时响应
171
+ set_mock_response(
172
+ 'bmc.kepler.persistence',
173
+ 'bmc.kepler.persistence|/bmc/kepler/persistence|bmc.kepler.persistence|BatchRead',
174
+ {'type': 'timeout'}
175
+ )
176
+ """
177
+ # 通过 D-Bus 接口调用(跨进程)
178
+ try:
179
+ # 尝试导入 DBusLibrary(可能不存在,需要处理)
180
+ try:
181
+ from bmcgo.component.fixture.dbus_library import DBusLibrary
182
+ dbus_lib = DBusLibrary()
183
+
184
+ response_json = json.dumps(response)
185
+ args_json = json.dumps(args) if args is not None else ''
186
+ success = dbus_lib.call_dbus_method(
187
+ MOCK_CONTROL_SERVICE,
188
+ MOCK_CONTROL_OBJECT_PATH,
189
+ MOCK_CONTROL_INTERFACE,
190
+ 'set_mock_response',
191
+ service_name,
192
+ lookup_key,
193
+ response_json,
194
+ args_json,
195
+ match_mode
196
+ )
197
+ if success:
198
+ logging.info(f'✅ 通过 D-Bus 接口设置 mock: {service_name} {lookup_key}')
199
+ else:
200
+ logging.warning(f'⚠️ 通过 D-Bus 接口设置 mock 失败,回退到本地设置')
201
+ set_runtime_mock(service_name, lookup_key, response, args, match_mode)
202
+ except ImportError:
203
+ # DBusLibrary 不可用,回退到本地设置
204
+ logging.warning(f'⚠️ DBusLibrary 不可用,使用本地设置(仅当前进程有效)')
205
+ set_runtime_mock(service_name, lookup_key, response, args, match_mode)
206
+ except Exception as e:
207
+ logging.warning(f'⚠️ 通过 D-Bus 接口设置 mock 失败: {e},回退到本地设置')
208
+ set_runtime_mock(service_name, lookup_key, response, args, match_mode)
209
+
210
+
211
+ def clear_mock(service_name=None, lookup_key=None):
212
+ """清除运行时 mock 数据(公共接口)
213
+
214
+ 这是 clear_runtime_mock 的公共接口,供用例代码使用。
215
+
216
+ 注意:由于 dbus_gateway.py 运行在独立进程中,通过 D-Bus 接口调用。
217
+
218
+ Args:
219
+ service_name: 服务名称,如果为 None 则清除所有服务的 mock
220
+ lookup_key: 方法键,如果为 None 则清除该服务的所有 mock
221
+
222
+ 示例:
223
+ # 清除特定方法的 mock
224
+ clear_mock('bmc.kepler.persistence', 'bmc.kepler.persistence| \
225
+ /bmc/kepler/persistence|bmc.kepler.persistence|BatchRead')
226
+
227
+ # 清除整个服务的 mock
228
+ clear_mock('bmc.kepler.persistence')
229
+
230
+ # 清除所有 mock
231
+ clear_mock()
232
+ """
233
+ # 通过 D-Bus 接口调用(跨进程)
234
+ try:
235
+ # 尝试导入 DBusLibrary(可能不存在,需要处理)
236
+ try:
237
+ from bmcgo.component.fixture.dbus_library import DBusLibrary
238
+ dbus_lib = DBusLibrary()
239
+ service_name_str = service_name if service_name else ''
240
+ lookup_key_str = lookup_key if lookup_key else ''
241
+ success = dbus_lib.call_dbus_method(
242
+ MOCK_CONTROL_SERVICE,
243
+ MOCK_CONTROL_OBJECT_PATH,
244
+ MOCK_CONTROL_INTERFACE,
245
+ 'clear_mock',
246
+ service_name_str,
247
+ lookup_key_str
248
+ )
249
+ if success:
250
+ logging.info(f'✅ 通过 D-Bus 接口清除 mock: service_name={service_name}, lookup_key={lookup_key}')
251
+ else:
252
+ logging.warning(f'⚠️ 通过 D-Bus 接口清除 mock 失败,回退到本地清除')
253
+ clear_runtime_mock(service_name, lookup_key)
254
+ except ImportError:
255
+ # DBusLibrary 不可用,回退到本地清除
256
+ logging.warning(f'⚠️ DBusLibrary 不可用,使用本地清除(仅当前进程有效)')
257
+ clear_runtime_mock(service_name, lookup_key)
258
+ except Exception as e:
259
+ logging.warning(f'⚠️ 通过 D-Bus 接口清除 mock 失败: {e},回退到本地清除')
260
+ clear_runtime_mock(service_name, lookup_key)
261
+
262
+
263
+ class DBusMockUtils:
264
+ """DBUS打桩服务的参数匹配和响应处理公共工具类"""
265
+ def __init__(self, service_name, dbus_default_mock_path=None):
266
+ self.service_name = service_name
267
+ self.dbus_default_mock_path = dbus_default_mock_path
268
+ # 初始化三个mock数据存储变量
269
+ self.custom_mock_data = {}
270
+ self.common_mock_data = {}
271
+ self.default_mock_data = {}
272
+ # 调用计数器:记录每个 lookup_key 的调用次数,用于按顺序返回响应
273
+ # 格式:{lookup_key: {call_index: count}}
274
+ # call_index 是参数组合的唯一标识(基于参数的哈希或序列化)
275
+ self._call_counters = {}
276
+ # 动态加载mock数据
277
+ self._load_mock_data_from_paths()
278
+
279
+ @staticmethod
280
+ def match_args_by_position(business_args, req_business_args):
281
+ """按位置匹配业务参数
282
+
283
+ Args:
284
+ business_args: 实际的业务参数列表,实际类型
285
+ req_business_args: 运行日志中的业务参数列表,字符串类型还未转换
286
+ Returns:
287
+ bool: 参数是否匹配
288
+ """
289
+ # 首先检查参数数量是否匹配
290
+ if len(business_args) != len(req_business_args):
291
+ return False
292
+
293
+ # 按位置逐一比较参数
294
+ length_array = range(len(business_args))
295
+ for i in length_array:
296
+ b_arg = business_args[i]
297
+ r_arg = req_business_args[i]
298
+ # 使用 DBusTypeConverter.dbus_string_to_type 将字符串参数转换为正确的类型
299
+ try:
300
+ # 将 req_business_args 的字符串参数转换为正确的 D-Bus 类型
301
+ converted_r_arg = BusCtlTypeConverter.dbus_string_to_type(r_arg)
302
+ except Exception as e:
303
+ logging.error(f"转换参数出错: {e}")
304
+ # 转换失败时,尝试使用原始字符串进行比较
305
+ if str(b_arg) != str(r_arg):
306
+ return False
307
+ continue
308
+ # 现在比较原始参数和转换后的参数
309
+ if not DBusMockUtils._compare_dbus_objects(b_arg, converted_r_arg):
310
+ return False
311
+ return True
312
+
313
+ @staticmethod
314
+ def _compare_dbus_objects(obj1, obj2):
315
+ """比较两个 D-Bus 对象是否相等
316
+
317
+ 委托给 BusCtlTypeConverter 的通用实现,包含字符串数组集合比较功能
318
+ """
319
+ # 委托给通用实现
320
+ return BusCtlTypeConverter.compare_dbus_objects(obj1, obj2)
321
+
322
+ @staticmethod
323
+ def _get_call_index(business_args):
324
+ """生成参数组合的唯一标识,用于区分不同的参数组合"""
325
+ normalized_args = []
326
+ try:
327
+ # 将参数序列化为字符串作为唯一标识
328
+ # 对于复杂对象,使用 json.dumps 序列化
329
+ for arg in business_args:
330
+ if isinstance(arg, (dict, list)):
331
+ normalized_args.append(json.dumps(arg, sort_keys=True))
332
+ else:
333
+ normalized_args.append(str(arg))
334
+ return '|'.join(normalized_args)
335
+ except Exception:
336
+ # 如果序列化失败,使用字符串表示
337
+ return str(business_args)
338
+
339
+ def match_params_and_get_response(self, method_key, args):
340
+ """动态匹配参数并返回响应,支持按调用顺序返回
341
+ 优先级:运行时 mock > 默认 mock 数据
342
+ Args:
343
+ method_key: 方法键,格式:'service|path|interface|method'
344
+ args: 方法参数列表
345
+ Returns:
346
+ 响应数据(dict),如果未找到则返回None
347
+ """
348
+ # 过滤实际传入的参数(如果有)
349
+ business_args = []
350
+ if args:
351
+ for arg in args:
352
+ logging.info(f"arg type: {type(arg)}, arg value: {str(arg)}")
353
+ # 过滤掉caller和source参数
354
+ if isinstance(arg, str) and ('caller' not in arg.lower() and 'source' not in arg.lower()):
355
+ business_args.append(arg) # 保留原始字符串类型
356
+ else:
357
+ business_args.append(arg) # 保留原始类型
358
+ logging.info(f'🔍 方法调用: {method_key}, 业务参数: {business_args}')
359
+ # 优先检查运行时 mock 数据
360
+ runtime_mock_data = get_runtime_mock_data(self.service_name)
361
+ if runtime_mock_data and method_key in runtime_mock_data:
362
+ logging.info(f'🔍 尝试匹配运行时 mock 数据')
363
+ record = self._match_record_by_call_params(runtime_mock_data, method_key, business_args)
364
+ if record:
365
+ logging.info(f'✅ 使用运行时 mock 响应')
366
+ return DBusResponseHandler.process_response(record['response'])
367
+
368
+ # 尝试匹配默认 mock 数据
369
+ logging.info(f'🔍 尝试匹配默认 mock 数据')
370
+ record = self._match_record_by_call_params(self.default_mock_data, method_key, business_args)
371
+ if record:
372
+ # 返回对应的响应
373
+ return DBusResponseHandler.process_response(record['response'])
374
+ # 未找到精确匹配,打印警告日志并返回空
375
+ logging.warning(f'⚠️ 未找到精确匹配的参数组合,method_key: {method_key},提供的参数: {business_args}')
376
+ # 无预设响应
377
+ logging.error(f'❌ 无预设响应,返回None')
378
+ return None
379
+
380
+ def clear_call_counters(self, lookup_key=None):
381
+ """清除调用计数器的公共方法
382
+
383
+ Args:
384
+ lookup_key: 方法键,如果为None则清除所有调用计数器
385
+ """
386
+ if lookup_key is None:
387
+ # 清除所有调用计数器
388
+ self._call_counters.clear()
389
+ else:
390
+ # 清除特定方法的调用计数器(counter_key 格式是 "method_key|call_index")
391
+ keys_to_remove = [k for k in self._call_counters.keys() if k.startswith(lookup_key + '|')]
392
+ for key in keys_to_remove:
393
+ del self._call_counters[key]
394
+
395
+ def get_call_counter_keys(self):
396
+ """获取所有调用计数器键的公共方法
397
+
398
+ Returns:
399
+ list: 调用计数器的所有键
400
+ """
401
+ return list(self._call_counters.keys())
402
+
403
+ def _load_mock_data_from_paths(self):
404
+ """从多个路径加载mock数据到不同的变量中"""
405
+ # 按优先级加载数据到不同的mock_data变量
406
+ service_dir = self.service_name.replace('.', '_')
407
+ # 加载默认mock数据
408
+ if self.dbus_default_mock_path and os.path.exists(self.dbus_default_mock_path):
409
+ service_mock_path = os.path.join(self.dbus_default_mock_path, service_dir, CommonConfig.DBUS_MOCK_DATA_FILE_NAME)
410
+ self.default_mock_data = self._load_single_mock_data(service_mock_path, "default")
411
+
412
+ def _load_single_mock_data(self, file_path, data_type):
413
+ """加载单个mock数据文件"""
414
+ mock_data = {}
415
+ if os.path.exists(file_path):
416
+ try:
417
+ with open(file_path, 'r', encoding='utf-8') as mock_file:
418
+ data = json.load(mock_file)
419
+ # 直接使用完整的lookup_key作为method_key
420
+ for lookup_key, records in data.items():
421
+ if lookup_key.startswith(f'{self.service_name}|'):
422
+ try:
423
+ # 不再提取interface和member,直接使用完整的lookup_key
424
+ mock_data[lookup_key] = records
425
+ except ValueError:
426
+ continue
427
+ logging.info(f"✅ 成功从 {file_path} 加载{data_type} mock数据")
428
+ except Exception as e:
429
+ logging.error(f"⚠️ 从 {file_path} 加载{data_type} mock数据失败: {e}")
430
+ else:
431
+ logging.warning(f"⚠️ {file_path} 不存在,跳过加载{data_type} mock数据")
432
+ return mock_data
433
+
434
+ def _match_record_by_call_params(self, mock_data, method_key, business_args, call_index=None):
435
+ """匹配记录并返回,支持按调用顺序返回
436
+
437
+ Args:
438
+ mock_data: mock数据字典
439
+ method_key: 方法键
440
+ business_args: 业务参数列表
441
+ call_index: 调用索引标识(如果为None,会自动生成)
442
+
443
+ Returns:
444
+ 匹配的记录,如果未找到则返回None
445
+ """
446
+ if not mock_data:
447
+ logging.error(f'❌ 未找到匹配的mock数据桩')
448
+ return None
449
+ # 直接查找完整的method_key
450
+ matched_records = mock_data.get(method_key, [])
451
+ if not matched_records:
452
+ logging.error(f'❌ 未找到匹配的方法: {method_key}')
453
+ return None
454
+ logging.debug('🔍 找到 %s 条匹配记录,开始逐一匹配参数', len(matched_records))
455
+ # 生成调用索引标识(如果未提供)
456
+ if call_index is None:
457
+ call_index = DBusMockUtils._get_call_index(business_args)
458
+ # 初始化调用计数器
459
+ counter_key = f"{method_key}|{call_index}"
460
+ if counter_key not in self._call_counters:
461
+ self._call_counters[counter_key] = 0
462
+ call_count = self._call_counters[counter_key]
463
+ # 先找到所有参数匹配的记录索引
464
+ matching_indices = []
465
+ for idx, record in enumerate(matched_records):
466
+ # 卫语句:如果记录不完整,跳过当前循环
467
+ if 'request' not in record or 'args' not in record['request'] or 'response' not in record:
468
+ logging.debug('⚠️ 记录 %s 不完整,跳过', idx)
469
+ continue
470
+ # 检查是否是运行时 mock 且 match_mode='any'
471
+ match_mode = record.get('_match_mode', 'exact')
472
+ if match_mode == 'any':
473
+ # 匹配任意参数,直接记录索引
474
+ matching_indices.append(idx)
475
+ continue
476
+ # 处理请求参数,正确识别多行数组参数
477
+ processed_req_args = record['request']['args']
478
+ # 如果运行时 mock 的 args 为 None,匹配所有参数
479
+ if record.get('_runtime_mock') and processed_req_args == [] and business_args == []:
480
+ matching_indices.append(idx)
481
+ continue
482
+ # 提取请求中的业务参数(过滤caller和source)
483
+ req_business_args = []
484
+ for req_arg in processed_req_args:
485
+ if isinstance(req_arg, str) and ('caller' not in req_arg.lower() and 'source' not in req_arg.lower()):
486
+ req_business_args.append(req_arg)
487
+ # 剔除business_args多余的参数
488
+ check_args = business_args[:]
489
+ if len(processed_req_args) > len(req_business_args) and len(check_args) > len(req_business_args):
490
+ check_args = check_args[len(check_args) - len(req_business_args):]
491
+ # 卫语句:如果参数数量不匹配,跳过当前循环
492
+ if len(check_args) > 0 and len(req_business_args) > 0 and len(check_args) != len(req_business_args):
493
+ continue
494
+ # 当有参数需要匹配时,检查参数内容是否匹配
495
+ if len(check_args) > 0 and len(req_business_args) > 0:
496
+ if not DBusMockUtils.match_args_by_position(check_args, req_business_args):
497
+ continue
498
+ # 参数匹配成功,记录索引
499
+ matching_indices.append(idx)
500
+ if not matching_indices:
501
+ logging.warning(f'❌ 所有记录都不匹配')
502
+ return None
503
+ # 根据调用次数选择对应的记录
504
+ if call_count >= len(matching_indices):
505
+ # 如果调用次数超过匹配记录数,使用最后一条记录(循环使用)
506
+ selected_idx = matching_indices[-1]
507
+ logging.warning(f'⚠️ 调用次数 {call_count} 超过匹配记录数 {len(matching_indices)},使用最后一条记录')
508
+ else:
509
+ selected_idx = matching_indices[call_count]
510
+ # 增加调用计数
511
+ self._call_counters[counter_key] = call_count + 1
512
+ selected_record = matched_records[selected_idx]
513
+ logging.info(f'✅ 记录 {selected_idx} 匹配成功(第 {call_count + 1} 次调用,参数组合: {call_index[:50]}...)')
514
+ return selected_record
@@ -0,0 +1,138 @@
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
+ """
13
+ DBus响应处理器 - 专门用于处理DBus响应的工具模块
14
+ 支持D-Bus中所有基本类型和复杂类型的正确解析和转换
15
+ """
16
+ import logging
17
+ from copy import deepcopy
18
+ from typing import Any, Dict
19
+
20
+ from bmcgo.component.fixture.busctl_type_converter import BusCtlTypeConverter
21
+
22
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
23
+
24
+
25
+
26
+ class DBusResponseHandler:
27
+ """
28
+ DBus响应处理器类,提供响应处理和值转换的功能
29
+ 支持所有D-Bus标准数据类型
30
+ """
31
+ @staticmethod
32
+ def process_response(response):
33
+ """处理响应并返回适当的结果
34
+
35
+ 支持的响应类型:
36
+ - method_return: 正常方法返回
37
+ - error: D-Bus 错误响应
38
+ - no_reply: 不发送响应(用于模拟超时)
39
+ - timeout: 模拟超时(等同于 no_reply)
40
+ - interrupt: 模拟中断(发送错误后中断连接)
41
+ - delay: 延迟响应(需要在调用端处理延迟)
42
+
43
+ Returns:
44
+ dict: 处理后的响应字典,包含 type、values、signature 等字段
45
+ None: 对于 no_reply、timeout 等不需要响应的类型
46
+ """
47
+ response_type = response.get('type')
48
+ # 处理no_reply类型响应(不发送响应,模拟超时)
49
+ if response_type == 'no_reply':
50
+ logging.info(f'🔇 no_reply: 不发送响应(模拟超时)')
51
+ return {'type': 'no_reply'}
52
+ # 处理timeout类型响应(等同于no_reply)
53
+ if response_type == 'timeout':
54
+ logging.info(f'⏱️ timeout: 模拟超时,不发送响应')
55
+ return {'type': 'timeout'}
56
+ # 处理error类型响应
57
+ if response_type == 'error':
58
+ error_name = response.get('error_name', 'org.freedesktop.DBus.Error.Failed')
59
+ error_message = response.get('error_message', 'Mock error response')
60
+ logging.error(f'❌ 错误响应: {error_name}')
61
+ logging.error(f' 错误详情: {error_message}')
62
+ return {
63
+ 'type': 'error',
64
+ 'error_name': error_name,
65
+ 'error_message': error_message
66
+ }
67
+ # 处理interrupt类型响应(模拟中断)
68
+ if response_type == 'interrupt':
69
+ logging.warning(f'⚠️ interrupt: 模拟中断,发送错误后中断连接')
70
+ return {
71
+ 'type': 'interrupt',
72
+ 'error_name': response.get('error_name', 'org.freedesktop.DBus.Error.Failed'),
73
+ 'error_message': response.get('error_message', 'Connection interrupted')
74
+ }
75
+ # 处理delay类型响应(延迟响应)
76
+ if response_type == 'delay':
77
+ delay_ms = response.get('delay_ms', 0)
78
+ delay_seconds = response.get('delay_seconds', 0)
79
+ actual_delay = delay_seconds if delay_seconds > 0 else (delay_ms / 1000.0 if delay_ms > 0 else 0)
80
+ logging.info(f'⏳ delay: 延迟响应 {actual_delay} 秒')
81
+ # 返回延迟配置和原始响应
82
+ processed = deepcopy(response)
83
+ processed['delay_seconds'] = actual_delay
84
+ return processed
85
+ # 处理method_return类型响应或包含values的响应(兼容没有type字段的情况)
86
+ elif (response.get('type') == 'method_return' or response.get('type') is None) and 'values' in response:
87
+ response_values = response['values']
88
+ logging.info(f'✅ 匹配成功,原始响应值: {response_values}, 类型: {type(response_values)}')
89
+ # 转换响应值为正确的类型
90
+ # mock_data中的values是busctl格式的字符串,需要使用BusCtlTypeConverter
91
+ # 运行时mock中的values可能是Python类型,直接使用
92
+ converted_values = []
93
+ for idx, value in enumerate(response_values):
94
+ logging.info(f'处理响应值[{idx}]: 类型={type(value)}, 值={ \
95
+ str(value)[:200] if isinstance(value, str) and len(str(value)) > 200 else value}')
96
+ # 如果已经是Python类型(不是字符串,或者是字符串但不是busctl格式),直接使用
97
+ if not isinstance(value, str):
98
+ # 已经是Python类型(dict, list, int, bool等),直接使用
99
+ converted_values.append(value)
100
+ logging.info(f'✅ 值[{idx}] 是Python类型,直接使用: {type(value)}')
101
+ elif isinstance(value, str) and (value.strip().startswith(('STRING ', 'ARRAY ', 'DICT_ENTRY ', \
102
+ 'STRUCT ', 'VARIANT ', 'BYTE ', 'INT16 ', 'UINT16 ', 'INT32 ', 'UINT32 ',\
103
+ 'INT64 ', 'UINT64 ', 'DOUBLE ', 'FLOAT ', 'BOOLEAN ', 'OBJECT_PATH ', 'SIGNATURE ')) \
104
+ or ('ARRAY "' in value and '{' in value) or ('DICT_ENTRY "' in value and '{' in value) \
105
+ or ('STRUCT "' in value and '{' in value) or ('VARIANT "' in value)):
106
+ # busctl格式字符串,需要转换
107
+ try:
108
+ converted_value = BusCtlTypeConverter.dbus_string_to_type(value)
109
+ converted_values.append(converted_value)
110
+ logging.info(f'✅ 值[{idx}] 是busctl格式,已转换: {type(converted_value)}')
111
+ except Exception as e:
112
+ logging.warning(f"使用BusCtlTypeConverter转换失败: {e},尝试直接使用原始值")
113
+ converted_values.append(value)
114
+ else:
115
+ # 普通字符串,检查是否需要JSON解析
116
+ # 只有明显的JSON格式(以{或[开头)才尝试解析
117
+ if value.strip().startswith(('{', '[')):
118
+ try:
119
+ import json
120
+ parsed = json.loads(value)
121
+ converted_values.append(parsed)
122
+ logging.info(f"✅ 值[{idx}] 是JSON格式,已解析: {type(parsed)}")
123
+ except Exception as e:
124
+ # JSON解析失败,直接使用原始字符串
125
+ logging.info(f"ℹ️ 值[{idx}] JSON解析失败,使用原始字符串: {value[:50]}")
126
+ converted_values.append(value)
127
+ else:
128
+ # 普通字符串(如ID、名称等),直接使用,不需要警告
129
+ logging.info(f"✅ 值[{idx}] 是普通字符串,直接使用: {value[:50]}")
130
+ converted_values.append(value)
131
+ logging.info(f'✅ 转换后响应值: {converted_values}, 类型: {[type(v) for v in converted_values]}')
132
+ processed_response: Dict[str, Any] = deepcopy(response)
133
+ processed_response['values'] = converted_values
134
+ return processed_response
135
+ # 其他类型响应
136
+ else:
137
+ logging.info(f'ℹ️ 未处理的响应类型: {response.get("type")}')
138
+ return None