pytest-dsl 0.3.1__py3-none-any.whl → 0.5.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/core/dsl_executor.py +161 -48
- pytest_dsl/core/lexer.py +12 -1
- pytest_dsl/core/parser.py +25 -6
- pytest_dsl/core/parsetab.py +71 -66
- pytest_dsl/keywords/http_keywords.py +89 -89
- pytest_dsl/keywords/system_keywords.py +326 -0
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/METADATA +159 -11
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/RECORD +12 -12
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/WHEEL +1 -1
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/entry_points.txt +1 -0
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {pytest_dsl-0.3.1.dist-info → pytest_dsl-0.5.0.dist-info}/top_level.txt +0 -0
pytest_dsl/core/dsl_executor.py
CHANGED
@@ -11,6 +11,7 @@ from pytest_dsl.core.context import TestContext
|
|
11
11
|
import pytest_dsl.keywords
|
12
12
|
from pytest_dsl.core.yaml_vars import yaml_vars
|
13
13
|
from pytest_dsl.core.variable_utils import VariableReplacer
|
14
|
+
from pytest_dsl.remote.keyword_client import remote_keyword_manager
|
14
15
|
|
15
16
|
|
16
17
|
class DSLExecutor:
|
@@ -27,7 +28,7 @@ class DSLExecutor:
|
|
27
28
|
self.test_context.executor = self # 让 test_context 能够访问到 executor
|
28
29
|
self.variable_replacer = VariableReplacer(self.variables, self.test_context)
|
29
30
|
self.imported_files = set() # 跟踪已导入的文件,避免循环导入
|
30
|
-
|
31
|
+
|
31
32
|
def set_current_data(self, data):
|
32
33
|
"""设置当前测试数据集"""
|
33
34
|
if data:
|
@@ -35,30 +36,30 @@ class DSLExecutor:
|
|
35
36
|
# 同时将数据添加到测试上下文
|
36
37
|
for key, value in data.items():
|
37
38
|
self.test_context.set(key, value)
|
38
|
-
|
39
|
+
|
39
40
|
def _load_test_data(self, data_source):
|
40
41
|
"""加载测试数据
|
41
|
-
|
42
|
+
|
42
43
|
:param data_source: 数据源配置,包含 file 和 format 字段
|
43
44
|
:return: 包含测试数据的列表
|
44
45
|
"""
|
45
46
|
if not data_source:
|
46
47
|
return [{}] # 如果没有数据源,返回一个空的数据集
|
47
|
-
|
48
|
+
|
48
49
|
file_path = data_source['file']
|
49
50
|
format_type = data_source['format']
|
50
|
-
|
51
|
+
|
51
52
|
if not os.path.exists(file_path):
|
52
53
|
raise Exception(f"数据文件不存在: {file_path}")
|
53
|
-
|
54
|
+
|
54
55
|
if format_type.lower() == 'csv':
|
55
56
|
return self._load_csv_data(file_path)
|
56
57
|
else:
|
57
58
|
raise Exception(f"不支持的数据格式: {format_type}")
|
58
|
-
|
59
|
+
|
59
60
|
def _load_csv_data(self, file_path):
|
60
61
|
"""加载CSV格式的测试数据
|
61
|
-
|
62
|
+
|
62
63
|
:param file_path: CSV文件路径
|
63
64
|
:return: 包含测试数据的列表
|
64
65
|
"""
|
@@ -68,11 +69,11 @@ class DSLExecutor:
|
|
68
69
|
for row in reader:
|
69
70
|
data_sets.append(row)
|
70
71
|
return data_sets
|
71
|
-
|
72
|
+
|
72
73
|
def eval_expression(self, expr_node):
|
73
74
|
"""
|
74
75
|
对表达式节点进行求值,返回表达式的值。
|
75
|
-
|
76
|
+
|
76
77
|
:param expr_node: AST中的表达式节点
|
77
78
|
:return: 表达式求值后的结果
|
78
79
|
:raises Exception: 当遇到未定义变量或无法求值的类型时抛出异常
|
@@ -101,7 +102,7 @@ class DSLExecutor:
|
|
101
102
|
return self._eval_arithmetic_expr(expr_node)
|
102
103
|
else:
|
103
104
|
raise Exception(f"无法求值的表达式类型: {expr_node.type}")
|
104
|
-
|
105
|
+
|
105
106
|
def _eval_expression_value(self, value):
|
106
107
|
"""处理表达式值的具体逻辑"""
|
107
108
|
if isinstance(value, Node):
|
@@ -110,7 +111,7 @@ class DSLExecutor:
|
|
110
111
|
# 如果是ID类型的变量名
|
111
112
|
if value in self.variable_replacer.local_variables:
|
112
113
|
return self.variable_replacer.local_variables[value]
|
113
|
-
|
114
|
+
|
114
115
|
# 定义变量引用模式
|
115
116
|
pattern = r'\$\{([a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*)\}'
|
116
117
|
# 检查整个字符串是否完全匹配单一变量引用模式
|
@@ -122,24 +123,24 @@ class DSLExecutor:
|
|
122
123
|
# 如果不是单一变量,则替换字符串中的所有变量引用
|
123
124
|
return self.variable_replacer.replace_in_string(value)
|
124
125
|
return value
|
125
|
-
|
126
|
+
|
126
127
|
def _eval_comparison_expr(self, expr_node):
|
127
128
|
"""
|
128
129
|
对比较表达式进行求值
|
129
|
-
|
130
|
+
|
130
131
|
:param expr_node: 比较表达式节点
|
131
132
|
:return: 比较结果(布尔值)
|
132
133
|
"""
|
133
134
|
left_value = self.eval_expression(expr_node.children[0])
|
134
135
|
right_value = self.eval_expression(expr_node.children[1])
|
135
136
|
operator = expr_node.value # 操作符: >, <, >=, <=, ==, !=
|
136
|
-
|
137
|
+
|
137
138
|
# 尝试类型转换
|
138
139
|
if isinstance(left_value, str) and str(left_value).isdigit():
|
139
140
|
left_value = int(left_value)
|
140
141
|
if isinstance(right_value, str) and str(right_value).isdigit():
|
141
142
|
right_value = int(right_value)
|
142
|
-
|
143
|
+
|
143
144
|
# 根据操作符执行相应的比较操作
|
144
145
|
if operator == '>':
|
145
146
|
return left_value > right_value
|
@@ -155,31 +156,31 @@ class DSLExecutor:
|
|
155
156
|
return left_value != right_value
|
156
157
|
else:
|
157
158
|
raise Exception(f"未知的比较操作符: {operator}")
|
158
|
-
|
159
|
+
|
159
160
|
def _eval_arithmetic_expr(self, expr_node):
|
160
161
|
"""
|
161
162
|
对算术表达式进行求值
|
162
|
-
|
163
|
+
|
163
164
|
:param expr_node: 算术表达式节点
|
164
165
|
:return: 计算结果
|
165
166
|
"""
|
166
167
|
left_value = self.eval_expression(expr_node.children[0])
|
167
168
|
right_value = self.eval_expression(expr_node.children[1])
|
168
169
|
operator = expr_node.value # 操作符: +, -, *, /
|
169
|
-
|
170
|
+
|
170
171
|
# 尝试类型转换 - 如果是字符串数字则转为数字
|
171
172
|
if isinstance(left_value, str) and str(left_value).replace('.', '', 1).isdigit():
|
172
173
|
left_value = float(left_value)
|
173
174
|
# 如果是整数则转为整数
|
174
175
|
if left_value.is_integer():
|
175
176
|
left_value = int(left_value)
|
176
|
-
|
177
|
+
|
177
178
|
if isinstance(right_value, str) and str(right_value).replace('.', '', 1).isdigit():
|
178
179
|
right_value = float(right_value)
|
179
180
|
# 如果是整数则转为整数
|
180
181
|
if right_value.is_integer():
|
181
182
|
right_value = int(right_value)
|
182
|
-
|
183
|
+
|
183
184
|
# 进行相应的算术运算
|
184
185
|
if operator == '+':
|
185
186
|
# 对于字符串,+是连接操作
|
@@ -202,18 +203,46 @@ class DSLExecutor:
|
|
202
203
|
return left_value / right_value
|
203
204
|
else:
|
204
205
|
raise Exception(f"未知的算术操作符: {operator}")
|
205
|
-
|
206
|
+
|
206
207
|
def _get_variable(self, var_name):
|
207
208
|
"""获取变量值,优先从本地变量获取,如果不存在则尝试从全局上下文获取"""
|
208
209
|
return self.variable_replacer.get_variable(var_name)
|
209
|
-
|
210
|
+
|
210
211
|
def _replace_variables_in_string(self, value):
|
211
212
|
"""替换字符串中的变量引用"""
|
212
213
|
return self.variable_replacer.replace_in_string(value)
|
213
|
-
|
214
|
+
|
215
|
+
def _handle_remote_import(self, node):
|
216
|
+
"""处理远程关键字导入
|
217
|
+
|
218
|
+
Args:
|
219
|
+
node: RemoteImport节点
|
220
|
+
"""
|
221
|
+
remote_info = node.value
|
222
|
+
url = self._replace_variables_in_string(remote_info['url'])
|
223
|
+
alias = remote_info['alias']
|
224
|
+
|
225
|
+
print(f"正在连接远程关键字服务器: {url}, 别名: {alias}")
|
226
|
+
|
227
|
+
# 注册远程服务器
|
228
|
+
success = remote_keyword_manager.register_remote_server(url, alias)
|
229
|
+
|
230
|
+
if not success:
|
231
|
+
print(f"无法连接到远程关键字服务器: {url}")
|
232
|
+
raise Exception(f"无法连接到远程关键字服务器: {url}")
|
233
|
+
|
234
|
+
print(f"已成功连接到远程关键字服务器: {url}, 别名: {alias}")
|
235
|
+
|
236
|
+
allure.attach(
|
237
|
+
f"已连接到远程关键字服务器: {url}\n"
|
238
|
+
f"别名: {alias}",
|
239
|
+
name="远程关键字导入",
|
240
|
+
attachment_type=allure.attachment_type.TEXT
|
241
|
+
)
|
242
|
+
|
214
243
|
def _handle_custom_keywords_in_file(self, node):
|
215
244
|
"""处理文件中的自定义关键字定义
|
216
|
-
|
245
|
+
|
217
246
|
Args:
|
218
247
|
node: Start节点
|
219
248
|
"""
|
@@ -233,7 +262,7 @@ class DSLExecutor:
|
|
233
262
|
self.test_context.clear()
|
234
263
|
metadata = {}
|
235
264
|
teardown_node = None
|
236
|
-
|
265
|
+
|
237
266
|
# 先处理元数据和找到teardown节点
|
238
267
|
for child in node.children:
|
239
268
|
if child.type == 'Metadata':
|
@@ -242,14 +271,17 @@ class DSLExecutor:
|
|
242
271
|
# 处理导入指令
|
243
272
|
if item.type == '@import':
|
244
273
|
self._handle_import(item.value)
|
274
|
+
# 处理远程关键字导入
|
275
|
+
elif item.type == 'RemoteImport':
|
276
|
+
self._handle_remote_import(item)
|
245
277
|
elif child.type == 'Teardown':
|
246
278
|
teardown_node = child
|
247
|
-
|
279
|
+
|
248
280
|
# 在_execute_test_iteration之前添加
|
249
281
|
self._handle_custom_keywords_in_file(node)
|
250
282
|
# 执行测试
|
251
283
|
self._execute_test_iteration(metadata, node, teardown_node)
|
252
|
-
|
284
|
+
|
253
285
|
except Exception as e:
|
254
286
|
# 如果是断言错误,直接抛出
|
255
287
|
if isinstance(e, AssertionError):
|
@@ -267,14 +299,14 @@ class DSLExecutor:
|
|
267
299
|
|
268
300
|
def _handle_import(self, file_path):
|
269
301
|
"""处理导入指令
|
270
|
-
|
302
|
+
|
271
303
|
Args:
|
272
304
|
file_path: 资源文件路径
|
273
305
|
"""
|
274
306
|
# 防止循环导入
|
275
307
|
if file_path in self.imported_files:
|
276
308
|
return
|
277
|
-
|
309
|
+
|
278
310
|
try:
|
279
311
|
# 导入自定义关键字文件
|
280
312
|
from pytest_dsl.core.custom_keyword_manager import custom_keyword_manager
|
@@ -297,12 +329,12 @@ class DSLExecutor:
|
|
297
329
|
if '@tags' in metadata:
|
298
330
|
for tag in metadata['@tags']:
|
299
331
|
allure.dynamic.tag(tag.value)
|
300
|
-
|
332
|
+
|
301
333
|
# 执行所有非teardown节点
|
302
334
|
for child in node.children:
|
303
335
|
if child.type != 'Teardown' and child.type != 'Metadata':
|
304
336
|
self.execute(child)
|
305
|
-
|
337
|
+
|
306
338
|
# 执行teardown
|
307
339
|
if teardown_node:
|
308
340
|
with allure.step("执行清理操作"):
|
@@ -320,7 +352,7 @@ class DSLExecutor:
|
|
320
352
|
# 否则清空变量(用于正常DSL执行)
|
321
353
|
import os
|
322
354
|
keep_variables = os.environ.get('PYTEST_DSL_KEEP_VARIABLES', '0') == '1'
|
323
|
-
|
355
|
+
|
324
356
|
if not keep_variables:
|
325
357
|
self.variables.clear()
|
326
358
|
# 同时清空测试上下文
|
@@ -336,7 +368,7 @@ class DSLExecutor:
|
|
336
368
|
"""处理赋值语句"""
|
337
369
|
var_name = node.value
|
338
370
|
expr_value = self.eval_expression(node.children[0])
|
339
|
-
|
371
|
+
|
340
372
|
# 检查变量名是否以g_开头,如果是则设置为全局变量
|
341
373
|
if var_name.startswith('g_'):
|
342
374
|
global_context.set_variable(var_name, expr_value)
|
@@ -361,7 +393,7 @@ class DSLExecutor:
|
|
361
393
|
var_name = node.value
|
362
394
|
keyword_call_node = node.children[0]
|
363
395
|
result = self.execute(keyword_call_node)
|
364
|
-
|
396
|
+
|
365
397
|
if result is not None:
|
366
398
|
# 检查变量名是否以g_开头,如果是则设置为全局变量
|
367
399
|
if var_name.startswith('g_'):
|
@@ -389,7 +421,7 @@ class DSLExecutor:
|
|
389
421
|
var_name = node.value
|
390
422
|
start = self.eval_expression(node.children[0])
|
391
423
|
end = self.eval_expression(node.children[1])
|
392
|
-
|
424
|
+
|
393
425
|
for i in range(int(start), int(end)):
|
394
426
|
# 存储在本地变量字典和测试上下文中
|
395
427
|
self.variable_replacer.local_variables[var_name] = i
|
@@ -403,9 +435,9 @@ class DSLExecutor:
|
|
403
435
|
keyword_info = keyword_manager.get_keyword_info(keyword_name)
|
404
436
|
if not keyword_info:
|
405
437
|
raise Exception(f"未注册的关键字: {keyword_name}")
|
406
|
-
|
438
|
+
|
407
439
|
kwargs = self._prepare_keyword_params(node, keyword_info)
|
408
|
-
|
440
|
+
|
409
441
|
try:
|
410
442
|
# 由于KeywordManager中的wrapper已经添加了allure.step和日志,这里不再重复添加
|
411
443
|
result = keyword_manager.execute(keyword_name, **kwargs)
|
@@ -418,7 +450,7 @@ class DSLExecutor:
|
|
418
450
|
"""准备关键字调用参数"""
|
419
451
|
mapping = keyword_info.get('mapping', {})
|
420
452
|
kwargs = {'context': self.test_context} # 默认传入context参数
|
421
|
-
|
453
|
+
|
422
454
|
# 检查是否有参数列表
|
423
455
|
if node.children[0]:
|
424
456
|
for param in node.children[0]:
|
@@ -427,7 +459,7 @@ class DSLExecutor:
|
|
427
459
|
# 对参数值进行变量替换
|
428
460
|
param_value = self.eval_expression(param.children[0])
|
429
461
|
kwargs[english_param_name] = param_value
|
430
|
-
|
462
|
+
|
431
463
|
return kwargs
|
432
464
|
|
433
465
|
@allure.step("执行清理操作")
|
@@ -438,10 +470,10 @@ class DSLExecutor:
|
|
438
470
|
@allure.step("执行返回语句")
|
439
471
|
def _handle_return(self, node):
|
440
472
|
"""处理return语句
|
441
|
-
|
473
|
+
|
442
474
|
Args:
|
443
475
|
node: Return节点
|
444
|
-
|
476
|
+
|
445
477
|
Returns:
|
446
478
|
表达式求值结果
|
447
479
|
"""
|
@@ -451,12 +483,12 @@ class DSLExecutor:
|
|
451
483
|
@allure.step("执行条件语句")
|
452
484
|
def _handle_if_statement(self, node):
|
453
485
|
"""处理if-else语句
|
454
|
-
|
486
|
+
|
455
487
|
Args:
|
456
488
|
node: IfStatement节点,包含条件表达式、if分支和可选的else分支
|
457
489
|
"""
|
458
490
|
condition = self.eval_expression(node.children[0])
|
459
|
-
|
491
|
+
|
460
492
|
# 将条件转换为布尔值进行评估
|
461
493
|
if condition:
|
462
494
|
# 执行if分支
|
@@ -466,10 +498,88 @@ class DSLExecutor:
|
|
466
498
|
# 如果存在else分支且条件为假,则执行else分支
|
467
499
|
with allure.step("执行else分支"):
|
468
500
|
return self.execute(node.children[2])
|
469
|
-
|
501
|
+
|
470
502
|
# 如果条件为假且没有else分支,则不执行任何操作
|
471
503
|
return None
|
472
504
|
|
505
|
+
def _execute_remote_keyword_call(self, node):
|
506
|
+
"""执行远程关键字调用
|
507
|
+
|
508
|
+
Args:
|
509
|
+
node: RemoteKeywordCall节点
|
510
|
+
|
511
|
+
Returns:
|
512
|
+
执行结果
|
513
|
+
"""
|
514
|
+
call_info = node.value
|
515
|
+
alias = call_info['alias']
|
516
|
+
keyword_name = call_info['keyword']
|
517
|
+
|
518
|
+
# 准备参数
|
519
|
+
params = []
|
520
|
+
if node.children and node.children[0]:
|
521
|
+
params = node.children[0]
|
522
|
+
|
523
|
+
kwargs = {}
|
524
|
+
for param in params:
|
525
|
+
param_name = param.value
|
526
|
+
param_value = self.eval_expression(param.children[0])
|
527
|
+
kwargs[param_name] = param_value
|
528
|
+
|
529
|
+
# 添加测试上下文
|
530
|
+
kwargs['context'] = self.test_context
|
531
|
+
|
532
|
+
with allure.step(f"执行远程关键字: {alias}|{keyword_name}"):
|
533
|
+
try:
|
534
|
+
# 执行远程关键字
|
535
|
+
result = remote_keyword_manager.execute_remote_keyword(alias, keyword_name, **kwargs)
|
536
|
+
allure.attach(
|
537
|
+
f"远程关键字参数: {kwargs}\n"
|
538
|
+
f"远程关键字结果: {result}",
|
539
|
+
name="远程关键字执行详情",
|
540
|
+
attachment_type=allure.attachment_type.TEXT
|
541
|
+
)
|
542
|
+
return result
|
543
|
+
except Exception as e:
|
544
|
+
# 记录错误并重新抛出
|
545
|
+
allure.attach(
|
546
|
+
f"远程关键字执行失败: {str(e)}",
|
547
|
+
name="远程关键字错误",
|
548
|
+
attachment_type=allure.attachment_type.TEXT
|
549
|
+
)
|
550
|
+
raise
|
551
|
+
|
552
|
+
def _handle_assignment_remote_keyword_call(self, node):
|
553
|
+
"""处理远程关键字调用赋值
|
554
|
+
|
555
|
+
Args:
|
556
|
+
node: AssignmentRemoteKeywordCall节点
|
557
|
+
"""
|
558
|
+
var_name = node.value
|
559
|
+
remote_keyword_call_node = node.children[0]
|
560
|
+
result = self.execute(remote_keyword_call_node)
|
561
|
+
|
562
|
+
if result is not None:
|
563
|
+
# 检查变量名是否以g_开头,如果是则设置为全局变量
|
564
|
+
if var_name.startswith('g_'):
|
565
|
+
global_context.set_variable(var_name, result)
|
566
|
+
allure.attach(
|
567
|
+
f"全局变量: {var_name}\n值: {result}",
|
568
|
+
name="全局变量赋值",
|
569
|
+
attachment_type=allure.attachment_type.TEXT
|
570
|
+
)
|
571
|
+
else:
|
572
|
+
# 存储在本地变量字典和测试上下文中
|
573
|
+
self.variable_replacer.local_variables[var_name] = result
|
574
|
+
self.test_context.set(var_name, result) # 同时添加到测试上下文
|
575
|
+
allure.attach(
|
576
|
+
f"变量: {var_name}\n值: {result}",
|
577
|
+
name="赋值详情",
|
578
|
+
attachment_type=allure.attachment_type.TEXT
|
579
|
+
)
|
580
|
+
else:
|
581
|
+
raise Exception(f"远程关键字没有返回结果")
|
582
|
+
|
473
583
|
def execute(self, node):
|
474
584
|
"""执行AST节点"""
|
475
585
|
handlers = {
|
@@ -483,9 +593,12 @@ class DSLExecutor:
|
|
483
593
|
'Teardown': self._handle_teardown,
|
484
594
|
'Return': self._handle_return,
|
485
595
|
'IfStatement': self._handle_if_statement,
|
486
|
-
'CustomKeyword': lambda _: None # 添加对CustomKeyword节点的处理,只需注册不需执行
|
596
|
+
'CustomKeyword': lambda _: None, # 添加对CustomKeyword节点的处理,只需注册不需执行
|
597
|
+
'RemoteImport': self._handle_remote_import,
|
598
|
+
'RemoteKeywordCall': self._execute_remote_keyword_call,
|
599
|
+
'AssignmentRemoteKeywordCall': self._handle_assignment_remote_keyword_call
|
487
600
|
}
|
488
|
-
|
601
|
+
|
489
602
|
handler = handlers.get(node.type)
|
490
603
|
if handler:
|
491
604
|
return handler(node)
|
@@ -494,4 +607,4 @@ class DSLExecutor:
|
|
494
607
|
def read_file(filename):
|
495
608
|
"""读取 DSL 文件内容"""
|
496
609
|
with open(filename, 'r', encoding='utf-8') as f:
|
497
|
-
return f.read()
|
610
|
+
return f.read()
|
pytest_dsl/core/lexer.py
CHANGED
@@ -12,7 +12,8 @@ reserved = {
|
|
12
12
|
'False': 'FALSE', # 添加布尔值支持
|
13
13
|
'return': 'RETURN', # 添加return关键字支持
|
14
14
|
'else': 'ELSE', # 添加else关键字支持
|
15
|
-
'if': 'IF' # 添加if关键字支持
|
15
|
+
'if': 'IF', # 添加if关键字支持
|
16
|
+
'as': 'AS' # 添加as关键字支持,用于远程关键字别名
|
16
17
|
}
|
17
18
|
|
18
19
|
# token 名称列表
|
@@ -38,6 +39,7 @@ tokens = [
|
|
38
39
|
'DATA_KEYWORD', # Add new token for @data keyword
|
39
40
|
'KEYWORD_KEYWORD', # 添加@keyword关键字
|
40
41
|
'IMPORT_KEYWORD', # 添加@import关键字
|
42
|
+
'REMOTE_KEYWORD', # 添加@remote关键字
|
41
43
|
'GT', # 大于 >
|
42
44
|
'LT', # 小于 <
|
43
45
|
'GE', # 大于等于 >=
|
@@ -48,6 +50,7 @@ tokens = [
|
|
48
50
|
'MINUS', # 减法 -
|
49
51
|
'TIMES', # 乘法 *
|
50
52
|
'DIVIDE', # 除法 /
|
53
|
+
'PIPE', # 管道符 |,用于远程关键字调用
|
51
54
|
] + list(reserved.values())
|
52
55
|
|
53
56
|
# 正则表达式定义 token
|
@@ -72,6 +75,14 @@ t_DIVIDE = r'/'
|
|
72
75
|
# 增加PLACEHOLDER规则,匹配 ${变量名} 格式
|
73
76
|
t_PLACEHOLDER = r'\$\{[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*\}'
|
74
77
|
|
78
|
+
# 添加管道符的正则表达式定义
|
79
|
+
t_PIPE = r'\|'
|
80
|
+
|
81
|
+
# 添加@remote关键字的token规则
|
82
|
+
def t_REMOTE_KEYWORD(t):
|
83
|
+
r'@remote'
|
84
|
+
return t
|
85
|
+
|
75
86
|
|
76
87
|
def t_DATE(t):
|
77
88
|
r'\d{4}-\d{2}-\d{2}(\s+\d{2}:\d{2}:\d{2})?'
|
pytest_dsl/core/parser.py
CHANGED
@@ -68,7 +68,8 @@ def p_metadata_item(p):
|
|
68
68
|
| AUTHOR_KEYWORD COLON metadata_value
|
69
69
|
| DATE_KEYWORD COLON DATE
|
70
70
|
| DATA_KEYWORD COLON data_source
|
71
|
-
| IMPORT_KEYWORD COLON STRING
|
71
|
+
| IMPORT_KEYWORD COLON STRING
|
72
|
+
| REMOTE_KEYWORD COLON STRING AS ID'''
|
72
73
|
if p[1] == '@tags':
|
73
74
|
p[0] = Node(p[1], value=p[4])
|
74
75
|
elif p[1] == '@data':
|
@@ -76,7 +77,12 @@ def p_metadata_item(p):
|
|
76
77
|
data_info = p[3] # 这是一个包含 file 和 format 的字典
|
77
78
|
p[0] = Node(p[1], value=data_info, children=None)
|
78
79
|
elif p[1] == '@import':
|
80
|
+
# 检查是否是远程导入格式
|
79
81
|
p[0] = Node(p[1], value=p[3])
|
82
|
+
elif p[1] == '@remote':
|
83
|
+
# 对于远程关键字导入,存储URL和别名
|
84
|
+
print(f"解析远程关键字导入: URL={p[3]}, 别名={p[5]}")
|
85
|
+
p[0] = Node('RemoteImport', value={'url': p[3], 'alias': p[5]})
|
80
86
|
else:
|
81
87
|
p[0] = Node(p[1], value=p[3])
|
82
88
|
|
@@ -114,6 +120,7 @@ def p_statements(p):
|
|
114
120
|
def p_statement(p):
|
115
121
|
'''statement : assignment
|
116
122
|
| keyword_call
|
123
|
+
| remote_keyword_call
|
117
124
|
| loop
|
118
125
|
| custom_keyword
|
119
126
|
| return_statement
|
@@ -123,9 +130,12 @@ def p_statement(p):
|
|
123
130
|
|
124
131
|
def p_assignment(p):
|
125
132
|
'''assignment : ID EQUALS expression
|
126
|
-
| ID EQUALS keyword_call
|
133
|
+
| ID EQUALS keyword_call
|
134
|
+
| ID EQUALS remote_keyword_call'''
|
127
135
|
if isinstance(p[3], Node) and p[3].type == 'KeywordCall':
|
128
136
|
p[0] = Node('AssignmentKeywordCall', [p[3]], p[1])
|
137
|
+
elif isinstance(p[3], Node) and p[3].type == 'RemoteKeywordCall':
|
138
|
+
p[0] = Node('AssignmentRemoteKeywordCall', [p[3]], p[1])
|
129
139
|
else:
|
130
140
|
p[0] = Node('Assignment', value=p[1], children=[p[3]])
|
131
141
|
|
@@ -284,7 +294,7 @@ def p_comparison_expr(p):
|
|
284
294
|
| expr_atom LE expr_atom
|
285
295
|
| expr_atom EQ expr_atom
|
286
296
|
| expr_atom NE expr_atom'''
|
287
|
-
|
297
|
+
|
288
298
|
# 根据规则索引判断使用的是哪个操作符
|
289
299
|
if p.slice[2].type == 'GT':
|
290
300
|
operator = '>'
|
@@ -301,7 +311,7 @@ def p_comparison_expr(p):
|
|
301
311
|
else:
|
302
312
|
print(f"警告: 无法识别的操作符类型 {p.slice[2].type}")
|
303
313
|
operator = None
|
304
|
-
|
314
|
+
|
305
315
|
p[0] = Node('ComparisonExpr', [p[1], p[3]], operator)
|
306
316
|
|
307
317
|
|
@@ -310,7 +320,7 @@ def p_arithmetic_expr(p):
|
|
310
320
|
| expression MINUS expression
|
311
321
|
| expression TIMES expression
|
312
322
|
| expression DIVIDE expression'''
|
313
|
-
|
323
|
+
|
314
324
|
# 根据规则索引判断使用的是哪个操作符
|
315
325
|
if p.slice[2].type == 'PLUS':
|
316
326
|
operator = '+'
|
@@ -323,7 +333,7 @@ def p_arithmetic_expr(p):
|
|
323
333
|
else:
|
324
334
|
print(f"警告: 无法识别的操作符类型 {p.slice[2].type}")
|
325
335
|
operator = None
|
326
|
-
|
336
|
+
|
327
337
|
p[0] = Node('ArithmeticExpr', [p[1], p[3]], operator)
|
328
338
|
|
329
339
|
|
@@ -337,3 +347,12 @@ def p_error(p):
|
|
337
347
|
|
338
348
|
def get_parser(debug=False):
|
339
349
|
return yacc.yacc(debug=debug)
|
350
|
+
|
351
|
+
# 定义远程关键字调用的语法规则
|
352
|
+
def p_remote_keyword_call(p):
|
353
|
+
'''remote_keyword_call : ID PIPE LBRACKET ID RBRACKET COMMA parameter_list
|
354
|
+
| ID PIPE LBRACKET ID RBRACKET'''
|
355
|
+
if len(p) == 8:
|
356
|
+
p[0] = Node('RemoteKeywordCall', [p[7]], {'alias': p[1], 'keyword': p[4]})
|
357
|
+
else:
|
358
|
+
p[0] = Node('RemoteKeywordCall', [[]], {'alias': p[1], 'keyword': p[4]})
|