pytest-dsl 0.13.0__py3-none-any.whl → 0.15.0__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.
- pytest_dsl/__init__.py +190 -3
- pytest_dsl/cli.py +37 -260
- pytest_dsl/core/custom_keyword_manager.py +114 -14
- pytest_dsl/core/dsl_executor.py +549 -166
- pytest_dsl/core/dsl_executor_utils.py +21 -22
- pytest_dsl/core/execution_tracker.py +291 -0
- pytest_dsl/core/hook_manager.py +87 -0
- pytest_dsl/core/hookable_executor.py +134 -0
- pytest_dsl/core/hookable_keyword_manager.py +106 -0
- pytest_dsl/core/hookspecs.py +175 -0
- pytest_dsl/core/keyword_loader.py +402 -0
- pytest_dsl/core/keyword_manager.py +29 -23
- pytest_dsl/core/parser.py +94 -18
- pytest_dsl/core/validator.py +417 -0
- pytest_dsl/core/yaml_loader.py +142 -42
- pytest_dsl/core/yaml_vars.py +90 -7
- pytest_dsl/plugin.py +10 -1
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/METADATA +1 -1
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/RECORD +23 -16
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/WHEEL +0 -0
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/entry_points.txt +0 -0
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.13.0.dist-info → pytest_dsl-0.15.0.dist-info}/top_level.txt +0 -0
@@ -15,10 +15,10 @@ parser = get_parser()
|
|
15
15
|
|
16
16
|
def read_file(filename):
|
17
17
|
"""读取DSL文件内容
|
18
|
-
|
18
|
+
|
19
19
|
Args:
|
20
20
|
filename: 文件路径
|
21
|
-
|
21
|
+
|
22
22
|
Returns:
|
23
23
|
str: 文件内容
|
24
24
|
"""
|
@@ -26,28 +26,27 @@ def read_file(filename):
|
|
26
26
|
return f.read()
|
27
27
|
|
28
28
|
|
29
|
-
def execute_dsl_file(filename: str) -> None:
|
29
|
+
def execute_dsl_file(filename: str, executor: DSLExecutor = None) -> None:
|
30
30
|
"""执行DSL文件
|
31
|
-
|
31
|
+
|
32
32
|
Args:
|
33
33
|
filename: DSL文件路径
|
34
|
+
executor: 可选的DSL执行器实例,如果不提供则创建新的
|
34
35
|
"""
|
35
36
|
try:
|
36
37
|
# 读取文件内容
|
37
38
|
with open(filename, 'r', encoding='utf-8') as f:
|
38
39
|
content = f.read()
|
39
|
-
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
executor.execute(ast)
|
50
|
-
|
40
|
+
|
41
|
+
# 创建或使用提供的执行器
|
42
|
+
if executor is None:
|
43
|
+
executor = DSLExecutor(enable_tracking=True)
|
44
|
+
|
45
|
+
# 使用带跟踪功能的execute_from_content方法
|
46
|
+
# 使用文件路径作为dsl_id以便于跟踪
|
47
|
+
dsl_id = str(Path(filename).resolve())
|
48
|
+
executor.execute_from_content(content, dsl_id=dsl_id)
|
49
|
+
|
51
50
|
except Exception as e:
|
52
51
|
# 如果是语法错误,记录并抛出
|
53
52
|
if "语法错误" in str(e):
|
@@ -59,18 +58,18 @@ def execute_dsl_file(filename: str) -> None:
|
|
59
58
|
|
60
59
|
def extract_metadata_from_ast(ast):
|
61
60
|
"""从AST中提取元数据
|
62
|
-
|
61
|
+
|
63
62
|
提取DSL文件中的元数据信息,如@data和@name标记。
|
64
|
-
|
63
|
+
|
65
64
|
Args:
|
66
65
|
ast: 解析后的抽象语法树
|
67
|
-
|
66
|
+
|
68
67
|
Returns:
|
69
68
|
tuple: (data_source, test_title) 元组,如果不存在则为None
|
70
69
|
"""
|
71
70
|
data_source = None
|
72
71
|
test_title = None
|
73
|
-
|
72
|
+
|
74
73
|
for child in ast.children:
|
75
74
|
if child.type == 'Metadata':
|
76
75
|
for item in child.children:
|
@@ -80,5 +79,5 @@ def extract_metadata_from_ast(ast):
|
|
80
79
|
test_title = item.value
|
81
80
|
if data_source and test_title:
|
82
81
|
break
|
83
|
-
|
84
|
-
return data_source, test_title
|
82
|
+
|
83
|
+
return data_source, test_title
|
@@ -0,0 +1,291 @@
|
|
1
|
+
"""
|
2
|
+
DSL执行跟踪器
|
3
|
+
负责跟踪DSL执行过程中的行号、状态等信息,并提供回调接口
|
4
|
+
"""
|
5
|
+
|
6
|
+
import time
|
7
|
+
import threading
|
8
|
+
from typing import Dict, Any, List, Optional, Callable
|
9
|
+
from dataclasses import dataclass, field
|
10
|
+
from enum import Enum
|
11
|
+
|
12
|
+
|
13
|
+
class ExecutionStatus(Enum):
|
14
|
+
"""执行状态枚举"""
|
15
|
+
PENDING = "pending" # 等待执行
|
16
|
+
RUNNING = "running" # 正在执行
|
17
|
+
SUCCESS = "success" # 执行成功
|
18
|
+
FAILED = "failed" # 执行失败
|
19
|
+
SKIPPED = "skipped" # 跳过执行
|
20
|
+
|
21
|
+
|
22
|
+
@dataclass
|
23
|
+
class ExecutionStep:
|
24
|
+
"""执行步骤信息"""
|
25
|
+
line_number: int
|
26
|
+
node_type: str
|
27
|
+
description: str
|
28
|
+
status: ExecutionStatus = ExecutionStatus.PENDING
|
29
|
+
start_time: Optional[float] = None
|
30
|
+
end_time: Optional[float] = None
|
31
|
+
error: Optional[str] = None
|
32
|
+
result: Optional[Any] = None
|
33
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
34
|
+
|
35
|
+
@property
|
36
|
+
def duration(self) -> Optional[float]:
|
37
|
+
"""计算执行耗时"""
|
38
|
+
if self.start_time and self.end_time:
|
39
|
+
return self.end_time - self.start_time
|
40
|
+
return None
|
41
|
+
|
42
|
+
def start(self):
|
43
|
+
"""开始执行"""
|
44
|
+
self.status = ExecutionStatus.RUNNING
|
45
|
+
self.start_time = time.time()
|
46
|
+
|
47
|
+
def finish(self, result: Any = None, error: str = None):
|
48
|
+
"""完成执行"""
|
49
|
+
self.end_time = time.time()
|
50
|
+
if error:
|
51
|
+
self.status = ExecutionStatus.FAILED
|
52
|
+
self.error = error
|
53
|
+
else:
|
54
|
+
self.status = ExecutionStatus.SUCCESS
|
55
|
+
self.result = result
|
56
|
+
|
57
|
+
def skip(self, reason: str = None):
|
58
|
+
"""跳过执行"""
|
59
|
+
self.status = ExecutionStatus.SKIPPED
|
60
|
+
if reason:
|
61
|
+
self.metadata['skip_reason'] = reason
|
62
|
+
|
63
|
+
|
64
|
+
class ExecutionTracker:
|
65
|
+
"""DSL执行跟踪器"""
|
66
|
+
|
67
|
+
def __init__(self, dsl_id: str = None):
|
68
|
+
self.dsl_id = dsl_id
|
69
|
+
self.steps: List[ExecutionStep] = []
|
70
|
+
self.current_step: Optional[ExecutionStep] = None
|
71
|
+
self.execution_start_time: Optional[float] = None
|
72
|
+
self.execution_end_time: Optional[float] = None
|
73
|
+
self.callbacks: Dict[str, List[Callable]] = {
|
74
|
+
'step_start': [],
|
75
|
+
'step_finish': [],
|
76
|
+
'execution_start': [],
|
77
|
+
'execution_finish': [],
|
78
|
+
'line_change': []
|
79
|
+
}
|
80
|
+
self._lock = threading.Lock()
|
81
|
+
|
82
|
+
# 执行统计
|
83
|
+
self.total_steps = 0
|
84
|
+
self.completed_steps = 0
|
85
|
+
self.failed_steps = 0
|
86
|
+
|
87
|
+
def register_callback(self, event: str, callback: Callable):
|
88
|
+
"""注册回调函数
|
89
|
+
|
90
|
+
Args:
|
91
|
+
event: 事件类型 (step_start, step_finish, execution_start,
|
92
|
+
execution_finish, line_change)
|
93
|
+
callback: 回调函数
|
94
|
+
"""
|
95
|
+
if event in self.callbacks:
|
96
|
+
self.callbacks[event].append(callback)
|
97
|
+
|
98
|
+
def _trigger_callbacks(self, event: str, **kwargs):
|
99
|
+
"""触发回调函数"""
|
100
|
+
for callback in self.callbacks.get(event, []):
|
101
|
+
try:
|
102
|
+
callback(tracker=self, **kwargs)
|
103
|
+
except Exception as e:
|
104
|
+
# 回调异常不应影响主执行流程
|
105
|
+
print(f"回调函数执行异常: {e}")
|
106
|
+
|
107
|
+
def start_execution(self):
|
108
|
+
"""开始执行"""
|
109
|
+
with self._lock:
|
110
|
+
self.execution_start_time = time.time()
|
111
|
+
self._trigger_callbacks('execution_start')
|
112
|
+
|
113
|
+
def finish_execution(self):
|
114
|
+
"""完成执行"""
|
115
|
+
with self._lock:
|
116
|
+
self.execution_end_time = time.time()
|
117
|
+
self._trigger_callbacks('execution_finish')
|
118
|
+
|
119
|
+
def start_step(self, line_number: int, node_type: str,
|
120
|
+
description: str, **metadata) -> ExecutionStep:
|
121
|
+
"""开始执行步骤"""
|
122
|
+
with self._lock:
|
123
|
+
step = ExecutionStep(
|
124
|
+
line_number=line_number,
|
125
|
+
node_type=node_type,
|
126
|
+
description=description,
|
127
|
+
metadata=metadata
|
128
|
+
)
|
129
|
+
step.start()
|
130
|
+
|
131
|
+
self.steps.append(step)
|
132
|
+
self.current_step = step
|
133
|
+
self.total_steps += 1
|
134
|
+
|
135
|
+
# 触发回调
|
136
|
+
self._trigger_callbacks('step_start', step=step)
|
137
|
+
self._trigger_callbacks('line_change',
|
138
|
+
line_number=line_number, step=step)
|
139
|
+
|
140
|
+
return step
|
141
|
+
|
142
|
+
def finish_current_step(self, result: Any = None, error: str = None):
|
143
|
+
"""完成当前步骤"""
|
144
|
+
with self._lock:
|
145
|
+
if self.current_step:
|
146
|
+
self.current_step.finish(result, error)
|
147
|
+
|
148
|
+
if error:
|
149
|
+
self.failed_steps += 1
|
150
|
+
else:
|
151
|
+
self.completed_steps += 1
|
152
|
+
|
153
|
+
# 触发回调
|
154
|
+
self._trigger_callbacks('step_finish', step=self.current_step)
|
155
|
+
|
156
|
+
# 记录到Allure
|
157
|
+
self._log_step_to_allure(self.current_step)
|
158
|
+
|
159
|
+
self.current_step = None
|
160
|
+
|
161
|
+
def skip_current_step(self, reason: str = None):
|
162
|
+
"""跳过当前步骤"""
|
163
|
+
with self._lock:
|
164
|
+
if self.current_step:
|
165
|
+
self.current_step.skip(reason)
|
166
|
+
self.completed_steps += 1
|
167
|
+
|
168
|
+
# 触发回调
|
169
|
+
self._trigger_callbacks('step_finish', step=self.current_step)
|
170
|
+
|
171
|
+
# 记录到Allure
|
172
|
+
self._log_step_to_allure(self.current_step)
|
173
|
+
|
174
|
+
self.current_step = None
|
175
|
+
|
176
|
+
def get_current_line(self) -> Optional[int]:
|
177
|
+
"""获取当前执行行号"""
|
178
|
+
return self.current_step.line_number if self.current_step else None
|
179
|
+
|
180
|
+
def get_execution_progress(self) -> Dict[str, Any]:
|
181
|
+
"""获取执行进度信息"""
|
182
|
+
return {
|
183
|
+
'dsl_id': self.dsl_id,
|
184
|
+
'total_steps': self.total_steps,
|
185
|
+
'completed_steps': self.completed_steps,
|
186
|
+
'failed_steps': self.failed_steps,
|
187
|
+
'current_line': self.get_current_line(),
|
188
|
+
'progress_percentage': (
|
189
|
+
(self.completed_steps / self.total_steps * 100)
|
190
|
+
if self.total_steps > 0 else 0),
|
191
|
+
'execution_time': self.get_execution_time()
|
192
|
+
}
|
193
|
+
|
194
|
+
def get_execution_time(self) -> Optional[float]:
|
195
|
+
"""获取执行时间"""
|
196
|
+
if self.execution_start_time:
|
197
|
+
end_time = self.execution_end_time or time.time()
|
198
|
+
return end_time - self.execution_start_time
|
199
|
+
return None
|
200
|
+
|
201
|
+
def get_steps_summary(self) -> Dict[str, Any]:
|
202
|
+
"""获取步骤执行摘要"""
|
203
|
+
status_counts = {}
|
204
|
+
line_ranges = []
|
205
|
+
|
206
|
+
for step in self.steps:
|
207
|
+
# 统计状态
|
208
|
+
status = step.status.value
|
209
|
+
status_counts[status] = status_counts.get(status, 0) + 1
|
210
|
+
|
211
|
+
# 收集行号范围
|
212
|
+
line_ranges.append(step.line_number)
|
213
|
+
|
214
|
+
return {
|
215
|
+
'total_steps': len(self.steps),
|
216
|
+
'status_counts': status_counts,
|
217
|
+
'line_range': {
|
218
|
+
'start': min(line_ranges) if line_ranges else None,
|
219
|
+
'end': max(line_ranges) if line_ranges else None
|
220
|
+
},
|
221
|
+
'execution_time': self.get_execution_time(),
|
222
|
+
'steps': [
|
223
|
+
{
|
224
|
+
'line': step.line_number,
|
225
|
+
'type': step.node_type,
|
226
|
+
'status': step.status.value,
|
227
|
+
'duration': step.duration,
|
228
|
+
'description': step.description
|
229
|
+
} for step in self.steps
|
230
|
+
]
|
231
|
+
}
|
232
|
+
|
233
|
+
def _log_step_to_allure(self, step: ExecutionStep):
|
234
|
+
"""将步骤信息记录到Allure报告"""
|
235
|
+
# 不再创建额外的allure attachment,避免重复记录
|
236
|
+
# DSL执行器中已经有专门的attachment记录行号信息
|
237
|
+
pass
|
238
|
+
|
239
|
+
def export_execution_report(self) -> Dict[str, Any]:
|
240
|
+
"""导出执行报告"""
|
241
|
+
return {
|
242
|
+
'dsl_id': self.dsl_id,
|
243
|
+
'execution_summary': self.get_execution_progress(),
|
244
|
+
'steps_summary': self.get_steps_summary(),
|
245
|
+
'execution_timeline': [
|
246
|
+
{
|
247
|
+
'line_number': step.line_number,
|
248
|
+
'node_type': step.node_type,
|
249
|
+
'description': step.description,
|
250
|
+
'status': step.status.value,
|
251
|
+
'start_time': step.start_time,
|
252
|
+
'end_time': step.end_time,
|
253
|
+
'duration': step.duration,
|
254
|
+
'error': step.error,
|
255
|
+
'metadata': step.metadata
|
256
|
+
} for step in self.steps
|
257
|
+
]
|
258
|
+
}
|
259
|
+
|
260
|
+
|
261
|
+
# 全局跟踪器注册表
|
262
|
+
_global_trackers: Dict[str, ExecutionTracker] = {}
|
263
|
+
_tracker_lock = threading.Lock()
|
264
|
+
|
265
|
+
|
266
|
+
def get_or_create_tracker(dsl_id: str = None) -> ExecutionTracker:
|
267
|
+
"""获取或创建执行跟踪器"""
|
268
|
+
with _tracker_lock:
|
269
|
+
if dsl_id is None:
|
270
|
+
dsl_id = f"tracker_{int(time.time() * 1000)}"
|
271
|
+
|
272
|
+
if dsl_id not in _global_trackers:
|
273
|
+
_global_trackers[dsl_id] = ExecutionTracker(dsl_id)
|
274
|
+
|
275
|
+
return _global_trackers[dsl_id]
|
276
|
+
|
277
|
+
|
278
|
+
def get_tracker(dsl_id: str) -> Optional[ExecutionTracker]:
|
279
|
+
"""获取指定的执行跟踪器"""
|
280
|
+
return _global_trackers.get(dsl_id)
|
281
|
+
|
282
|
+
|
283
|
+
def remove_tracker(dsl_id: str):
|
284
|
+
"""移除执行跟踪器"""
|
285
|
+
with _tracker_lock:
|
286
|
+
_global_trackers.pop(dsl_id, None)
|
287
|
+
|
288
|
+
|
289
|
+
def list_active_trackers() -> List[str]:
|
290
|
+
"""列出所有活跃的跟踪器"""
|
291
|
+
return list(_global_trackers.keys())
|
@@ -0,0 +1,87 @@
|
|
1
|
+
"""
|
2
|
+
pytest-dsl hook管理器
|
3
|
+
|
4
|
+
管理插件的注册、发现和调用
|
5
|
+
"""
|
6
|
+
import pluggy
|
7
|
+
from typing import Optional, List, Any
|
8
|
+
from .hookspecs import DSLHookSpecs
|
9
|
+
|
10
|
+
|
11
|
+
class DSLHookManager:
|
12
|
+
"""DSL Hook管理器"""
|
13
|
+
|
14
|
+
_instance: Optional['DSLHookManager'] = None
|
15
|
+
|
16
|
+
def __init__(self):
|
17
|
+
self.pm: pluggy.PluginManager = pluggy.PluginManager("pytest_dsl")
|
18
|
+
self.pm.add_hookspecs(DSLHookSpecs)
|
19
|
+
self._initialized = False
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
def get_instance(cls) -> 'DSLHookManager':
|
23
|
+
"""获取单例实例"""
|
24
|
+
if cls._instance is None:
|
25
|
+
cls._instance = cls()
|
26
|
+
return cls._instance
|
27
|
+
|
28
|
+
def register_plugin(self, plugin: Any, name: Optional[str] = None) -> None:
|
29
|
+
"""注册插件
|
30
|
+
|
31
|
+
Args:
|
32
|
+
plugin: 插件实例或模块
|
33
|
+
name: 插件名称(可选)
|
34
|
+
"""
|
35
|
+
self.pm.register(plugin, name=name)
|
36
|
+
|
37
|
+
def unregister_plugin(self, plugin: Any = None,
|
38
|
+
name: Optional[str] = None) -> None:
|
39
|
+
"""注销插件
|
40
|
+
|
41
|
+
Args:
|
42
|
+
plugin: 插件实例或模块
|
43
|
+
name: 插件名称
|
44
|
+
"""
|
45
|
+
self.pm.unregister(plugin=plugin, name=name)
|
46
|
+
|
47
|
+
def is_registered(self, plugin: Any) -> bool:
|
48
|
+
"""检查插件是否已注册"""
|
49
|
+
return self.pm.is_registered(plugin)
|
50
|
+
|
51
|
+
def load_setuptools_entrypoints(self, group: str = "pytest_dsl") -> int:
|
52
|
+
"""加载setuptools入口点插件
|
53
|
+
|
54
|
+
Args:
|
55
|
+
group: 入口点组名
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
加载的插件数量
|
59
|
+
"""
|
60
|
+
return self.pm.load_setuptools_entrypoints(group)
|
61
|
+
|
62
|
+
def get_plugins(self) -> List[Any]:
|
63
|
+
"""获取所有已注册的插件"""
|
64
|
+
return self.pm.get_plugins()
|
65
|
+
|
66
|
+
def hook(self) -> Any:
|
67
|
+
"""获取hook调用器"""
|
68
|
+
return self.pm.hook
|
69
|
+
|
70
|
+
def initialize(self) -> None:
|
71
|
+
"""初始化hook管理器"""
|
72
|
+
if self._initialized:
|
73
|
+
return
|
74
|
+
|
75
|
+
# 尝试加载setuptools入口点插件
|
76
|
+
try:
|
77
|
+
loaded = self.load_setuptools_entrypoints()
|
78
|
+
if loaded > 0:
|
79
|
+
print(f"加载了 {loaded} 个插件")
|
80
|
+
except Exception as e:
|
81
|
+
print(f"加载插件时出现错误: {e}")
|
82
|
+
|
83
|
+
self._initialized = True
|
84
|
+
|
85
|
+
|
86
|
+
# 全局hook管理器实例
|
87
|
+
hook_manager = DSLHookManager.get_instance()
|
@@ -0,0 +1,134 @@
|
|
1
|
+
"""
|
2
|
+
可扩展的DSL执行器
|
3
|
+
|
4
|
+
支持hook机制的DSL执行器,提供统一的执行接口
|
5
|
+
"""
|
6
|
+
from typing import Dict, List, Optional, Any
|
7
|
+
from .dsl_executor import DSLExecutor
|
8
|
+
from .hook_manager import hook_manager
|
9
|
+
|
10
|
+
|
11
|
+
class HookableExecutor:
|
12
|
+
"""支持Hook机制的DSL执行器"""
|
13
|
+
|
14
|
+
def __init__(self):
|
15
|
+
self.executor = None
|
16
|
+
self._ensure_initialized()
|
17
|
+
|
18
|
+
def _ensure_initialized(self):
|
19
|
+
"""确保执行器已初始化"""
|
20
|
+
if self.executor is None:
|
21
|
+
self.executor = DSLExecutor(enable_hooks=True)
|
22
|
+
|
23
|
+
def execute_dsl(self, dsl_id: str, context: Optional[Dict[str, Any]] = None) -> Any:
|
24
|
+
"""执行DSL用例
|
25
|
+
|
26
|
+
Args:
|
27
|
+
dsl_id: DSL标识符
|
28
|
+
context: 执行上下文(可选)
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
执行结果
|
32
|
+
"""
|
33
|
+
self._ensure_initialized()
|
34
|
+
|
35
|
+
# 通过hook获取执行上下文扩展
|
36
|
+
if self.executor.enable_hooks and self.executor.hook_manager:
|
37
|
+
context_results = self.executor.hook_manager.pm.hook.dsl_get_execution_context(
|
38
|
+
dsl_id=dsl_id, base_context=context or {}
|
39
|
+
)
|
40
|
+
for extended_context in context_results:
|
41
|
+
if extended_context:
|
42
|
+
context = extended_context
|
43
|
+
break
|
44
|
+
|
45
|
+
# 执行DSL(内容为空,通过hook加载)
|
46
|
+
return self.executor.execute_from_content(
|
47
|
+
content="",
|
48
|
+
dsl_id=dsl_id,
|
49
|
+
context=context
|
50
|
+
)
|
51
|
+
|
52
|
+
def list_dsl_cases(self, project_id: Optional[int] = None,
|
53
|
+
filters: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
|
54
|
+
"""列出DSL用例
|
55
|
+
|
56
|
+
Args:
|
57
|
+
project_id: 项目ID(可选)
|
58
|
+
filters: 过滤条件(可选)
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
用例列表
|
62
|
+
"""
|
63
|
+
self._ensure_initialized()
|
64
|
+
|
65
|
+
if not (self.executor.enable_hooks and self.executor.hook_manager):
|
66
|
+
return []
|
67
|
+
|
68
|
+
all_cases = []
|
69
|
+
case_results = self.executor.hook_manager.pm.hook.dsl_list_cases(
|
70
|
+
project_id=project_id, filters=filters
|
71
|
+
)
|
72
|
+
|
73
|
+
for result in case_results:
|
74
|
+
if result:
|
75
|
+
all_cases.extend(result)
|
76
|
+
|
77
|
+
return all_cases
|
78
|
+
|
79
|
+
def validate_dsl_content(self, dsl_id: str, content: str) -> List[str]:
|
80
|
+
"""验证DSL内容
|
81
|
+
|
82
|
+
Args:
|
83
|
+
dsl_id: DSL标识符
|
84
|
+
content: DSL内容
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
验证错误列表,空列表表示验证通过
|
88
|
+
"""
|
89
|
+
self._ensure_initialized()
|
90
|
+
|
91
|
+
if not (self.executor.enable_hooks and self.executor.hook_manager):
|
92
|
+
return []
|
93
|
+
|
94
|
+
all_errors = []
|
95
|
+
validation_results = self.executor.hook_manager.pm.hook.dsl_validate_content(
|
96
|
+
dsl_id=dsl_id, content=content
|
97
|
+
)
|
98
|
+
|
99
|
+
for errors in validation_results:
|
100
|
+
if errors:
|
101
|
+
all_errors.extend(errors)
|
102
|
+
|
103
|
+
return all_errors
|
104
|
+
|
105
|
+
def transform_dsl_content(self, dsl_id: str, content: str) -> str:
|
106
|
+
"""转换DSL内容
|
107
|
+
|
108
|
+
Args:
|
109
|
+
dsl_id: DSL标识符
|
110
|
+
content: 原始DSL内容
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
转换后的DSL内容
|
114
|
+
"""
|
115
|
+
self._ensure_initialized()
|
116
|
+
|
117
|
+
if not (self.executor.enable_hooks and self.executor.hook_manager):
|
118
|
+
return content
|
119
|
+
|
120
|
+
transformed_content = content
|
121
|
+
transform_results = self.executor.hook_manager.pm.hook.dsl_transform_content(
|
122
|
+
dsl_id=dsl_id, content=content
|
123
|
+
)
|
124
|
+
|
125
|
+
for result in transform_results:
|
126
|
+
if result:
|
127
|
+
transformed_content = result
|
128
|
+
break
|
129
|
+
|
130
|
+
return transformed_content
|
131
|
+
|
132
|
+
|
133
|
+
# 全局实例
|
134
|
+
hookable_executor = HookableExecutor()
|
@@ -0,0 +1,106 @@
|
|
1
|
+
"""
|
2
|
+
可扩展的关键字管理器
|
3
|
+
|
4
|
+
支持通过hook机制注册自定义关键字
|
5
|
+
"""
|
6
|
+
from typing import Dict, List, Optional, Any
|
7
|
+
from .keyword_manager import keyword_manager
|
8
|
+
from .hook_manager import hook_manager
|
9
|
+
|
10
|
+
|
11
|
+
class HookableKeywordManager:
|
12
|
+
"""支持Hook机制的关键字管理器"""
|
13
|
+
|
14
|
+
def __init__(self):
|
15
|
+
self.hook_keywords = {} # 存储通过hook注册的关键字
|
16
|
+
self._initialized = False
|
17
|
+
|
18
|
+
def initialize(self):
|
19
|
+
"""初始化,调用hook注册关键字"""
|
20
|
+
if self._initialized:
|
21
|
+
return
|
22
|
+
|
23
|
+
if hook_manager and hook_manager._initialized:
|
24
|
+
try:
|
25
|
+
# 调用hook注册自定义关键字
|
26
|
+
hook_manager.pm.hook.dsl_register_custom_keywords()
|
27
|
+
print(f"通过Hook注册了 {len(self.hook_keywords)} 个自定义关键字")
|
28
|
+
except Exception as e:
|
29
|
+
print(f"Hook关键字注册失败: {e}")
|
30
|
+
|
31
|
+
self._initialized = True
|
32
|
+
|
33
|
+
def register_hook_keyword(self, keyword_name: str, dsl_content: str,
|
34
|
+
source_info: Optional[Dict[str, Any]] = None):
|
35
|
+
"""通过Hook注册自定义关键字
|
36
|
+
|
37
|
+
Args:
|
38
|
+
keyword_name: 关键字名称
|
39
|
+
dsl_content: DSL内容定义
|
40
|
+
source_info: 来源信息
|
41
|
+
"""
|
42
|
+
# 检查是否已经注册过
|
43
|
+
if keyword_name in self.hook_keywords:
|
44
|
+
print(f"Hook关键字 {keyword_name} 已存在,跳过重复注册")
|
45
|
+
return
|
46
|
+
|
47
|
+
# 使用custom_keyword_manager的公共方法注册关键字
|
48
|
+
try:
|
49
|
+
from .custom_keyword_manager import custom_keyword_manager
|
50
|
+
|
51
|
+
# 准备来源名称
|
52
|
+
source_name = (source_info.get('source_name', 'Hook插件')
|
53
|
+
if source_info else 'Hook插件')
|
54
|
+
|
55
|
+
# 使用公共方法注册指定关键字
|
56
|
+
success = custom_keyword_manager.register_specific_keyword_from_dsl_content(
|
57
|
+
keyword_name, dsl_content, source_name
|
58
|
+
)
|
59
|
+
|
60
|
+
if success:
|
61
|
+
# 更新来源信息
|
62
|
+
if keyword_name in keyword_manager._keywords:
|
63
|
+
keyword_info = keyword_manager._keywords[keyword_name]
|
64
|
+
if source_info:
|
65
|
+
keyword_info.update(source_info)
|
66
|
+
else:
|
67
|
+
keyword_info.update({
|
68
|
+
'source_type': 'hook',
|
69
|
+
'source_name': 'Hook插件'
|
70
|
+
})
|
71
|
+
|
72
|
+
# 记录到hook关键字列表
|
73
|
+
self.hook_keywords[keyword_name] = {
|
74
|
+
'dsl_content': dsl_content,
|
75
|
+
'source_info': source_info or {
|
76
|
+
'source_type': 'hook',
|
77
|
+
'source_name': 'Hook插件'
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
print(f"注册Hook关键字: {keyword_name}")
|
82
|
+
|
83
|
+
except Exception as e:
|
84
|
+
print(f"注册Hook关键字失败 {keyword_name}: {e}")
|
85
|
+
raise
|
86
|
+
|
87
|
+
def get_hook_keywords(self) -> Dict[str, Dict]:
|
88
|
+
"""获取所有通过Hook注册的关键字"""
|
89
|
+
return self.hook_keywords.copy()
|
90
|
+
|
91
|
+
def is_hook_keyword(self, keyword_name: str) -> bool:
|
92
|
+
"""检查是否为Hook关键字"""
|
93
|
+
return keyword_name in self.hook_keywords
|
94
|
+
|
95
|
+
def unregister_hook_keyword(self, keyword_name: str):
|
96
|
+
"""注销Hook关键字"""
|
97
|
+
if keyword_name in self.hook_keywords:
|
98
|
+
del self.hook_keywords[keyword_name]
|
99
|
+
# 从关键字管理器中移除
|
100
|
+
if hasattr(keyword_manager, '_keywords'):
|
101
|
+
keyword_manager._keywords.pop(keyword_name, None)
|
102
|
+
print(f"注销Hook关键字: {keyword_name}")
|
103
|
+
|
104
|
+
|
105
|
+
# 全局实例
|
106
|
+
hookable_keyword_manager = HookableKeywordManager()
|