pytest-dsl 0.15.1__py3-none-any.whl → 0.15.2__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.
@@ -31,6 +31,28 @@ class ReturnException(Exception):
31
31
  super().__init__(f"Return with value: {return_value}")
32
32
 
33
33
 
34
+ class DSLExecutionError(Exception):
35
+ """DSL执行异常,包含行号信息"""
36
+
37
+ def __init__(self, message: str, line_number: int = None, node_type: str = None,
38
+ original_exception: Exception = None):
39
+ self.line_number = line_number
40
+ self.node_type = node_type
41
+ self.original_exception = original_exception
42
+
43
+ # 构建详细的错误消息
44
+ error_parts = [message]
45
+ if line_number:
46
+ error_parts.append(f"行号: {line_number}")
47
+ if node_type:
48
+ error_parts.append(f"节点类型: {node_type}")
49
+ if original_exception:
50
+ error_parts.append(
51
+ f"原始异常: {type(original_exception).__name__}: {str(original_exception)}")
52
+
53
+ super().__init__(" | ".join(error_parts))
54
+
55
+
34
56
  class DSLExecutor:
35
57
  """DSL执行器,负责执行解析后的AST
36
58
 
@@ -62,9 +84,140 @@ class DSLExecutor:
62
84
  self.enable_tracking = enable_tracking
63
85
  self.execution_tracker: ExecutionTracker = None
64
86
 
87
+ # 当前执行节点(用于异常处理时获取行号)
88
+ self._current_node = None
89
+ # 节点调用栈,用于追踪有行号信息的节点
90
+ self._node_stack = []
91
+
65
92
  if self.enable_hooks:
66
93
  self._init_hooks()
67
94
 
95
+ def _get_line_info(self, node=None):
96
+ """获取行号信息字符串
97
+
98
+ Args:
99
+ node: 可选的节点,如果不提供则使用当前节点
100
+
101
+ Returns:
102
+ 包含行号信息的字符串
103
+ """
104
+ target_node = node or self._current_node
105
+
106
+ # 尝试从当前节点获取行号
107
+ if target_node and hasattr(target_node, 'line_number') and target_node.line_number:
108
+ return f"\n行号: {target_node.line_number}"
109
+
110
+ # 如果当前节点没有行号,从节点栈中查找最近的有行号的节点
111
+ for stack_node in reversed(self._node_stack):
112
+ if hasattr(stack_node, 'line_number') and stack_node.line_number:
113
+ return f"\n行号: {stack_node.line_number}"
114
+
115
+ # 如果当前节点没有行号,尝试从当前执行的节点获取
116
+ if self._current_node and hasattr(self._current_node, 'line_number') and self._current_node.line_number:
117
+ return f"\n行号: {self._current_node.line_number}"
118
+
119
+ return ""
120
+
121
+ def _handle_exception_with_line_info(self, e: Exception, node=None, context_info: str = "", skip_allure_logging: bool = False):
122
+ """统一处理异常并记录行号信息
123
+
124
+ Args:
125
+ e: 原始异常
126
+ node: 可选的节点,用于获取行号
127
+ context_info: 额外的上下文信息
128
+ skip_allure_logging: 是否跳过Allure日志记录,避免重复记录
129
+
130
+ Raises:
131
+ DSLExecutionError: 包含行号信息的DSL执行异常
132
+ """
133
+ target_node = node or self._current_node
134
+ line_number = None
135
+ node_type = None
136
+
137
+ # 尝试从目标节点获取行号
138
+ if target_node:
139
+ line_number = getattr(target_node, 'line_number', None)
140
+ node_type = getattr(target_node, 'type', None)
141
+
142
+ # 如果目标节点没有行号,从节点栈中查找最近的有行号的节点
143
+ if not line_number:
144
+ for stack_node in reversed(self._node_stack):
145
+ stack_line = getattr(stack_node, 'line_number', None)
146
+ if stack_line:
147
+ line_number = stack_line
148
+ if not node_type:
149
+ node_type = getattr(stack_node, 'type', None)
150
+ break
151
+
152
+ # 如果还是没有行号,尝试从当前执行节点获取
153
+ if not line_number and self._current_node:
154
+ line_number = getattr(self._current_node, 'line_number', None)
155
+ if not node_type:
156
+ node_type = getattr(self._current_node, 'type', None)
157
+
158
+ # 构建错误消息
159
+ error_msg = str(e)
160
+ if context_info:
161
+ error_msg = f"{context_info}: {error_msg}"
162
+
163
+ # 只有在没有跳过Allure日志记录时才记录到Allure
164
+ if not skip_allure_logging:
165
+ # 记录到Allure
166
+ line_info = self._get_line_info(target_node)
167
+ error_details = f"{error_msg}{line_info}"
168
+ if context_info:
169
+ error_details += f"\n上下文: {context_info}"
170
+
171
+ allure.attach(
172
+ error_details,
173
+ name="DSL执行异常",
174
+ attachment_type=allure.attachment_type.TEXT
175
+ )
176
+
177
+ # 如果原始异常已经是DSLExecutionError,不要重复封装
178
+ if isinstance(e, DSLExecutionError):
179
+ raise e
180
+
181
+ # 对于控制流异常,直接重抛,不封装
182
+ if isinstance(e, (BreakException, ContinueException, ReturnException)):
183
+ raise e
184
+
185
+ # 对于断言错误,保持原样但添加行号信息
186
+ if isinstance(e, AssertionError):
187
+ enhanced_msg = f"{str(e)}{self._get_line_info(target_node)}"
188
+ raise AssertionError(enhanced_msg) from e
189
+
190
+ # 其他异常封装为DSLExecutionError
191
+ raise DSLExecutionError(
192
+ message=error_msg,
193
+ line_number=line_number,
194
+ node_type=node_type,
195
+ original_exception=e
196
+ ) from e
197
+
198
+ def _execute_with_error_handling(self, func, node, *args, **kwargs):
199
+ """在错误处理包装器中执行函数
200
+
201
+ Args:
202
+ func: 要执行的函数
203
+ node: 当前节点
204
+ *args: 函数参数
205
+ **kwargs: 函数关键字参数
206
+
207
+ Returns:
208
+ 函数执行结果
209
+ """
210
+ old_node = self._current_node
211
+ self._current_node = node
212
+
213
+ try:
214
+ return func(*args, **kwargs)
215
+ except Exception as e:
216
+ self._handle_exception_with_line_info(
217
+ e, node, f"执行{getattr(node, 'type', '未知节点')}")
218
+ finally:
219
+ self._current_node = old_node
220
+
68
221
  def set_current_data(self, data):
69
222
  """设置当前测试数据集"""
70
223
  if data:
@@ -112,67 +265,99 @@ class DSLExecutor:
112
265
 
113
266
  :param expr_node: AST中的表达式节点
114
267
  :return: 表达式求值后的结果
115
- :raises Exception: 当遇到未定义变量或无法求值的类型时抛出异常
268
+ :raises DSLExecutionError: 当遇到未定义变量或无法求值的类型时抛出异常
116
269
  """
117
- if expr_node.type == 'Expression':
118
- value = self._eval_expression_value(expr_node.value)
119
- # 统一处理变量替换
120
- return self.variable_replacer.replace_in_value(value)
121
- elif expr_node.type == 'KeywordCall':
122
- return self.execute(expr_node)
123
- elif expr_node.type == 'ListExpr':
124
- # 处理列表表达式
125
- result = []
126
- for item in expr_node.children:
127
- item_value = self.eval_expression(item)
128
- result.append(item_value)
129
- return result
130
- elif expr_node.type == 'DictExpr':
131
- # 处理字典表达式
132
- result = {}
133
- for item in expr_node.children:
134
- # 每个item是DictItem节点,包含键和值
135
- key_value = self.eval_expression(item.children[0])
136
- value_value = self.eval_expression(item.children[1])
137
- result[key_value] = value_value
138
- return result
139
- elif expr_node.type == 'BooleanExpr':
140
- # 处理布尔值表达式
141
- return expr_node.value
142
- elif expr_node.type == 'ComparisonExpr':
143
- # 处理比较表达式
144
- return self._eval_comparison_expr(expr_node)
145
- elif expr_node.type == 'ArithmeticExpr':
146
- # 处理算术表达式
147
- return self._eval_arithmetic_expr(expr_node)
148
- else:
149
- raise Exception(f"无法求值的表达式类型: {expr_node.type}")
270
+ def _eval_expression_impl():
271
+ if expr_node.type == 'Expression':
272
+ value = self._eval_expression_value(expr_node.value)
273
+ # 统一处理变量替换
274
+ return self.variable_replacer.replace_in_value(value)
275
+ elif expr_node.type == 'StringLiteral':
276
+ # 字符串字面量,如果包含变量占位符则进行替换,否则直接返回
277
+ if '${' in expr_node.value:
278
+ return self.variable_replacer.replace_in_string(expr_node.value)
279
+ else:
280
+ return expr_node.value
281
+ elif expr_node.type == 'NumberLiteral':
282
+ # 数字字面量,直接返回值
283
+ return expr_node.value
284
+ elif expr_node.type == 'VariableRef':
285
+ # 变量引用,从变量存储中获取值
286
+ var_name = expr_node.value
287
+ try:
288
+ return self.variable_replacer.get_variable(var_name)
289
+ except KeyError:
290
+ raise KeyError(f"变量 '{var_name}' 不存在")
291
+ elif expr_node.type == 'PlaceholderRef':
292
+ # 变量占位符 ${var},进行变量替换
293
+ return self.variable_replacer.replace_in_string(expr_node.value)
294
+ elif expr_node.type == 'KeywordCall':
295
+ return self.execute(expr_node)
296
+ elif expr_node.type == 'ListExpr':
297
+ # 处理列表表达式
298
+ result = []
299
+ for item in expr_node.children:
300
+ item_value = self.eval_expression(item)
301
+ result.append(item_value)
302
+ return result
303
+ elif expr_node.type == 'DictExpr':
304
+ # 处理字典表达式
305
+ result = {}
306
+ for item in expr_node.children:
307
+ # 每个item是DictItem节点,包含键和值
308
+ key_value = self.eval_expression(item.children[0])
309
+ value_value = self.eval_expression(item.children[1])
310
+ result[key_value] = value_value
311
+ return result
312
+ elif expr_node.type == 'BooleanExpr':
313
+ # 处理布尔值表达式
314
+ return expr_node.value
315
+ elif expr_node.type == 'ComparisonExpr':
316
+ # 处理比较表达式
317
+ return self._eval_comparison_expr(expr_node)
318
+ elif expr_node.type == 'ArithmeticExpr':
319
+ # 处理算术表达式
320
+ return self._eval_arithmetic_expr(expr_node)
321
+ else:
322
+ raise Exception(f"无法求值的表达式类型: {expr_node.type}")
323
+
324
+ return self._execute_with_error_handling(_eval_expression_impl, expr_node)
150
325
 
151
326
  def _eval_expression_value(self, value):
152
327
  """处理表达式值的具体逻辑"""
153
- if isinstance(value, Node):
154
- return self.eval_expression(value)
155
- elif isinstance(value, str):
156
- # 如果是ID类型的变量名
157
- if value in self.variable_replacer.local_variables:
158
- return self.variable_replacer.local_variables[value]
159
-
160
- # 定义扩展的变量引用模式,支持数组索引和字典键访问
161
- pattern = (
162
- r'\$\{([a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*'
163
- r'(?:(?:\.[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*)'
164
- r'|(?:\[[^\]]+\]))*)\}'
165
- )
166
- # 检查整个字符串是否完全匹配单一变量引用模式
167
- match = re.fullmatch(pattern, value)
168
- if match:
169
- var_ref = match.group(1)
170
- # 使用新的变量路径解析器
171
- return self.variable_replacer._parse_variable_path(var_ref)
172
- else:
173
- # 如果不是单一变量,则替换字符串中的所有变量引用
174
- return self.variable_replacer.replace_in_string(value)
175
- return value
328
+ try:
329
+ if isinstance(value, Node):
330
+ return self.eval_expression(value)
331
+ elif isinstance(value, str):
332
+ # 定义扩展的变量引用模式,支持数组索引和字典键访问
333
+ pattern = (
334
+ r'\$\{([a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*'
335
+ r'(?:(?:\.[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*)'
336
+ r'|(?:\[[^\]]+\]))*)\}'
337
+ )
338
+ # 检查整个字符串是否完全匹配单一变量引用模式
339
+ match = re.fullmatch(pattern, value)
340
+ if match:
341
+ var_ref = match.group(1)
342
+ # 使用新的变量路径解析器
343
+ return self.variable_replacer._parse_variable_path(var_ref)
344
+ elif '${' in value:
345
+ # 如果包含变量占位符,则替换字符串中的所有变量引用
346
+ return self.variable_replacer.replace_in_string(value)
347
+ else:
348
+ # 对于不包含 ${} 的普通字符串,检查是否为单纯的变量名
349
+ # 只有当字符串是有效的变量名格式且确实存在该变量时,才当作变量处理
350
+ if (re.match(r'^[a-zA-Z_\u4e00-\u9fa5][a-zA-Z0-9_\u4e00-\u9fa5]*$', value) and
351
+ value in self.variable_replacer.local_variables):
352
+ return self.variable_replacer.local_variables[value]
353
+ else:
354
+ # 否则当作字符串字面量处理
355
+ return value
356
+ return value
357
+ except Exception as e:
358
+ # 为变量解析异常添加更多上下文信息
359
+ context_info = f"解析表达式值 '{value}'"
360
+ self._handle_exception_with_line_info(e, context_info=context_info)
176
361
 
177
362
  def _eval_comparison_expr(self, expr_node):
178
363
  """
@@ -181,31 +366,35 @@ class DSLExecutor:
181
366
  :param expr_node: 比较表达式节点
182
367
  :return: 比较结果(布尔值)
183
368
  """
184
- left_value = self.eval_expression(expr_node.children[0])
185
- right_value = self.eval_expression(expr_node.children[1])
186
- operator = expr_node.value # 操作符: >, <, >=, <=, ==, !=
187
-
188
- # 尝试类型转换
189
- if isinstance(left_value, str) and str(left_value).isdigit():
190
- left_value = int(left_value)
191
- if isinstance(right_value, str) and str(right_value).isdigit():
192
- right_value = int(right_value)
193
-
194
- # 根据操作符执行相应的比较操作
195
- if operator == '>':
196
- return left_value > right_value
197
- elif operator == '<':
198
- return left_value < right_value
199
- elif operator == '>=':
200
- return left_value >= right_value
201
- elif operator == '<=':
202
- return left_value <= right_value
203
- elif operator == '==':
204
- return left_value == right_value
205
- elif operator == '!=':
206
- return left_value != right_value
207
- else:
208
- raise Exception(f"未知的比较操作符: {operator}")
369
+ try:
370
+ left_value = self.eval_expression(expr_node.children[0])
371
+ right_value = self.eval_expression(expr_node.children[1])
372
+ operator = expr_node.value # 操作符: >, <, >=, <=, ==, !=
373
+
374
+ # 尝试类型转换
375
+ if isinstance(left_value, str) and str(left_value).isdigit():
376
+ left_value = int(left_value)
377
+ if isinstance(right_value, str) and str(right_value).isdigit():
378
+ right_value = int(right_value)
379
+
380
+ # 根据操作符执行相应的比较操作
381
+ if operator == '>':
382
+ return left_value > right_value
383
+ elif operator == '<':
384
+ return left_value < right_value
385
+ elif operator == '>=':
386
+ return left_value >= right_value
387
+ elif operator == '<=':
388
+ return left_value <= right_value
389
+ elif operator == '==':
390
+ return left_value == right_value
391
+ elif operator == '!=':
392
+ return left_value != right_value
393
+ else:
394
+ raise Exception(f"未知的比较操作符: {operator}")
395
+ except Exception as e:
396
+ context_info = f"比较表达式求值 '{operator}'"
397
+ self._handle_exception_with_line_info(e, expr_node, context_info)
209
398
 
210
399
  def _eval_arithmetic_expr(self, expr_node):
211
400
  """
@@ -214,54 +403,58 @@ class DSLExecutor:
214
403
  :param expr_node: 算术表达式节点
215
404
  :return: 计算结果
216
405
  """
217
- left_value = self.eval_expression(expr_node.children[0])
218
- right_value = self.eval_expression(expr_node.children[1])
219
- operator = expr_node.value # 操作符: +, -, *, /, %
220
-
221
- # 尝试类型转换 - 如果是字符串数字则转为数字
222
- if (isinstance(left_value, str) and
223
- str(left_value).replace('.', '', 1).isdigit()):
224
- left_value = float(left_value)
225
- # 如果是整数则转为整数
226
- if left_value.is_integer():
227
- left_value = int(left_value)
228
-
229
- if (isinstance(right_value, str) and
230
- str(right_value).replace('.', '', 1).isdigit()):
231
- right_value = float(right_value)
232
- # 如果是整数则转为整数
233
- if right_value.is_integer():
234
- right_value = int(right_value)
406
+ try:
407
+ left_value = self.eval_expression(expr_node.children[0])
408
+ right_value = self.eval_expression(expr_node.children[1])
409
+ operator = expr_node.value # 操作符: +, -, *, /, %
235
410
 
236
- # 进行相应的算术运算
237
- if operator == '+':
238
- # 对于字符串,+是连接操作
239
- if isinstance(left_value, str) or isinstance(right_value, str):
240
- return str(left_value) + str(right_value)
241
- return left_value + right_value
242
- elif operator == '-':
243
- return left_value - right_value
244
- elif operator == '*':
245
- # 如果其中一个是字符串,另一个是数字,则进行字符串重复
411
+ # 尝试类型转换 - 如果是字符串数字则转为数字
246
412
  if (isinstance(left_value, str) and
247
- isinstance(right_value, (int, float))):
248
- return left_value * int(right_value)
249
- elif (isinstance(right_value, str) and
250
- isinstance(left_value, (int, float))):
251
- return right_value * int(left_value)
252
- return left_value * right_value
253
- elif operator == '/':
254
- # 除法时检查除数是否为0
255
- if right_value == 0:
256
- raise Exception("除法错误: 除数不能为0")
257
- return left_value / right_value
258
- elif operator == '%':
259
- # 模运算时检查除数是否为0
260
- if right_value == 0:
261
- raise Exception("模运算错误: 除数不能为0")
262
- return left_value % right_value
263
- else:
264
- raise Exception(f"未知的算术操作符: {operator}")
413
+ str(left_value).replace('.', '', 1).isdigit()):
414
+ left_value = float(left_value)
415
+ # 如果是整数则转为整数
416
+ if left_value.is_integer():
417
+ left_value = int(left_value)
418
+
419
+ if (isinstance(right_value, str) and
420
+ str(right_value).replace('.', '', 1).isdigit()):
421
+ right_value = float(right_value)
422
+ # 如果是整数则转为整数
423
+ if right_value.is_integer():
424
+ right_value = int(right_value)
425
+
426
+ # 进行相应的算术运算
427
+ if operator == '+':
428
+ # 对于字符串,+是连接操作
429
+ if isinstance(left_value, str) or isinstance(right_value, str):
430
+ return str(left_value) + str(right_value)
431
+ return left_value + right_value
432
+ elif operator == '-':
433
+ return left_value - right_value
434
+ elif operator == '*':
435
+ # 如果其中一个是字符串,另一个是数字,则进行字符串重复
436
+ if (isinstance(left_value, str) and
437
+ isinstance(right_value, (int, float))):
438
+ return left_value * int(right_value)
439
+ elif (isinstance(right_value, str) and
440
+ isinstance(left_value, (int, float))):
441
+ return right_value * int(left_value)
442
+ return left_value * right_value
443
+ elif operator == '/':
444
+ # 除法时检查除数是否为0
445
+ if right_value == 0:
446
+ raise Exception("除法错误: 除数不能为0")
447
+ return left_value / right_value
448
+ elif operator == '%':
449
+ # 模运算时检查除数是否为0
450
+ if right_value == 0:
451
+ raise Exception("模运算错误: 除数不能为0")
452
+ return left_value % right_value
453
+ else:
454
+ raise Exception(f"未知的算术操作符: {operator}")
455
+ except Exception as e:
456
+ context_info = f"算术表达式求值 '{operator}'"
457
+ self._handle_exception_with_line_info(e, expr_node, context_info)
265
458
 
266
459
  def _get_variable(self, var_name):
267
460
  """获取变量值,优先从本地变量获取,如果不存在则尝试从全局上下文获取"""
@@ -520,14 +713,18 @@ class DSLExecutor:
520
713
  def _handle_assignment(self, node):
521
714
  """处理赋值语句"""
522
715
  step_name = f"变量赋值: {node.value}"
523
- line_info = (f"\n行号: {node.line_number}"
524
- if hasattr(node, 'line_number') and node.line_number
525
- else "")
716
+ line_info = self._get_line_info(node)
526
717
 
527
718
  with allure.step(step_name):
528
719
  try:
529
720
  var_name = node.value
530
- expr_value = self.eval_expression(node.children[0])
721
+ # 在求值表达式之前,确保当前节点设置正确
722
+ old_current_node = self._current_node
723
+ self._current_node = node
724
+ try:
725
+ expr_value = self.eval_expression(node.children[0])
726
+ finally:
727
+ self._current_node = old_current_node
531
728
 
532
729
  # 检查变量名是否以g_开头,如果是则设置为全局变量
533
730
  if var_name.startswith('g_'):
@@ -550,172 +747,163 @@ class DSLExecutor:
550
747
  attachment_type=allure.attachment_type.TEXT
551
748
  )
552
749
  except Exception as e:
553
- # 记录赋值失败,包含行号信息
750
+ # 在步骤内部记录异常详情
751
+ error_details = f"执行Assignment节点: {str(e)}{line_info}\n上下文: 执行Assignment节点"
554
752
  allure.attach(
555
- f"变量: {var_name}\n错误: {str(e)}{line_info}",
556
- name="赋值失败",
753
+ error_details,
754
+ name="DSL执行异常",
557
755
  attachment_type=allure.attachment_type.TEXT
558
756
  )
757
+ # 重新抛出异常,让外层的统一异常处理机制处理
559
758
  raise
560
759
 
561
760
  def _handle_assignment_keyword_call(self, node):
562
- """处理关键字调用赋值"""
563
- step_name = f"关键字调用赋值: {node.value}"
564
- line_info = (f"\n行号: {node.line_number}"
565
- if hasattr(node, 'line_number') and node.line_number
566
- else "")
761
+ """处理关键字调用赋值
567
762
 
568
- with allure.step(step_name):
763
+ Args:
764
+ node: AssignmentKeywordCall节点
765
+ """
766
+ var_name = node.value
767
+ line_info = self._get_line_info(node)
768
+
769
+ with allure.step(f"关键字赋值: {var_name}"):
569
770
  try:
570
- var_name = node.value
571
771
  keyword_call_node = node.children[0]
572
772
  result = self.execute(keyword_call_node)
573
773
 
574
- if result is not None:
575
- # 处理新的统一返回格式(支持远程关键字模式)
576
- if isinstance(result, dict) and 'result' in result:
577
- # 提取主要返回值
578
- main_result = result['result']
579
-
580
- # 处理captures字段中的变量
581
- captures = result.get('captures', {})
582
- for capture_var, capture_value in captures.items():
583
- if capture_var.startswith('g_'):
584
- global_context.set_variable(
585
- capture_var, capture_value)
586
- else:
587
- self.variable_replacer.local_variables[
588
- capture_var] = capture_value
589
- self.test_context.set(
590
- capture_var, capture_value)
591
-
592
- # 将主要结果赋值给指定变量
593
- actual_result = main_result
594
- else:
595
- # 传统格式,直接使用结果
596
- actual_result = result
597
-
598
- # 检查变量名是否以g_开头,如果是则设置为全局变量
599
- if var_name.startswith('g_'):
600
- global_context.set_variable(var_name, actual_result)
601
- allure.attach(
602
- f"全局变量: {var_name}\n值: {actual_result}"
603
- f"{line_info}",
604
- name="关键字调用赋值",
605
- attachment_type=allure.attachment_type.TEXT
606
- )
607
- else:
608
- # 存储在本地变量字典和测试上下文中
609
- self.variable_replacer.local_variables[
610
- var_name] = actual_result
611
- self.test_context.set(var_name, actual_result)
612
- allure.attach(
613
- f"变量: {var_name}\n值: {actual_result}"
614
- f"{line_info}",
615
- name="关键字调用赋值",
616
- attachment_type=allure.attachment_type.TEXT
617
- )
618
- else:
619
- error_msg = f"关键字 {keyword_call_node.value} 没有返回结果"
774
+ # 检查变量名是否以g_开头,如果是则设置为全局变量
775
+ if var_name.startswith('g_'):
776
+ global_context.set_variable(var_name, result)
620
777
  allure.attach(
621
- f"变量: {var_name}\n错误: {error_msg}{line_info}",
622
- name="关键字调用赋值失败",
778
+ f"全局变量: {var_name}\n值: {result}{line_info}",
779
+ name="关键字赋值详情",
623
780
  attachment_type=allure.attachment_type.TEXT
624
781
  )
625
- raise Exception(error_msg)
626
- except Exception as e:
627
- # 如果异常不是我们刚才抛出的,记录异常信息
628
- if "没有返回结果" not in str(e):
782
+ else:
783
+ # 存储在本地变量字典和测试上下文中
784
+ self.variable_replacer.local_variables[var_name] = result
785
+ self.test_context.set(var_name, result)
786
+ # 记录关键字赋值,包含行号信息
629
787
  allure.attach(
630
- f"变量: {var_name}\n错误: {str(e)}{line_info}",
631
- name="关键字调用赋值失败",
788
+ f"变量: {var_name}\n值: {result}{line_info}",
789
+ name="关键字赋值详情",
632
790
  attachment_type=allure.attachment_type.TEXT
633
791
  )
792
+ except Exception as e:
793
+ # 在步骤内部记录异常详情
794
+ error_details = f"执行AssignmentKeywordCall节点: {str(e)}{line_info}\n上下文: 执行AssignmentKeywordCall节点"
795
+ allure.attach(
796
+ error_details,
797
+ name="DSL执行异常",
798
+ attachment_type=allure.attachment_type.TEXT
799
+ )
800
+ # 重新抛出异常,让外层的统一异常处理机制处理
634
801
  raise
635
802
 
636
803
  def _handle_for_loop(self, node):
637
804
  """处理for循环"""
638
- step_name = f"For循环: {node.value}"
639
- line_info = (f"\n行号: {node.line_number}"
640
- if hasattr(node, 'line_number') and node.line_number
641
- else "")
805
+ step_name = f"执行循环: {node.value}"
806
+ line_info = self._get_line_info(node)
642
807
 
643
808
  with allure.step(step_name):
644
809
  try:
645
810
  var_name = node.value
646
- start = self.eval_expression(node.children[0])
647
- end = self.eval_expression(node.children[1])
811
+ # 计算循环范围
812
+ loop_range = self.eval_expression(node.children[0])
813
+
814
+ # 如果是range对象,转换为列表
815
+ if hasattr(loop_range, '__iter__'):
816
+ loop_items = list(loop_range)
817
+ else:
818
+ loop_items = [loop_range]
648
819
 
649
- # 记录循环信息,包含行号
650
820
  allure.attach(
651
- f"循环变量: {var_name}\n范围: {start}{end}{line_info}",
652
- name="For循环",
821
+ f"循环变量: {var_name}\n循环范围: {loop_items}{line_info}",
822
+ name="循环信息",
653
823
  attachment_type=allure.attachment_type.TEXT
654
824
  )
825
+
826
+ for i in loop_items:
827
+ # 设置循环变量
828
+ self.variable_replacer.local_variables[var_name] = i
829
+ self.test_context.set(var_name, i)
830
+
831
+ with allure.step(f"循环轮次: {var_name} = {i}"):
832
+ try:
833
+ self.execute(node.children[2])
834
+ except BreakException:
835
+ # 遇到break语句,退出循环
836
+ allure.attach(
837
+ f"在 {var_name} = {i} 时遇到break语句,退出循环",
838
+ name="循环Break",
839
+ attachment_type=allure.attachment_type.TEXT
840
+ )
841
+ break
842
+ except ContinueException:
843
+ # 遇到continue语句,跳过本次循环
844
+ allure.attach(
845
+ f"在 {var_name} = {i} 时遇到continue语句,跳过本次循环",
846
+ name="循环Continue",
847
+ attachment_type=allure.attachment_type.TEXT
848
+ )
849
+ continue
850
+ except ReturnException as e:
851
+ # 遇到return语句,将异常向上传递
852
+ allure.attach(
853
+ f"在 {var_name} = {i} 时遇到return语句,退出函数",
854
+ name="循环Return",
855
+ attachment_type=allure.attachment_type.TEXT
856
+ )
857
+ raise e
858
+ except Exception as e:
859
+ # 在循环轮次内部记录异常详情
860
+ error_details = f"循环执行异常 ({var_name} = {i}): {str(e)}{line_info}\n上下文: 执行ForLoop节点"
861
+ allure.attach(
862
+ error_details,
863
+ name="DSL执行异常",
864
+ attachment_type=allure.attachment_type.TEXT
865
+ )
866
+ # 重新抛出异常
867
+ raise
868
+ except (BreakException, ContinueException, ReturnException):
869
+ # 这些控制流异常应该继续向上传递
870
+ raise
655
871
  except Exception as e:
656
- # 记录循环初始化失败
872
+ # 在步骤内部记录异常详情
873
+ error_details = f"执行ForLoop节点: {str(e)}{line_info}\n上下文: 执行ForLoop节点"
657
874
  allure.attach(
658
- f"循环变量: {var_name}\n错误: {str(e)}{line_info}",
659
- name="For循环初始化失败",
875
+ error_details,
876
+ name="DSL执行异常",
660
877
  attachment_type=allure.attachment_type.TEXT
661
878
  )
879
+ # 重新抛出异常,让外层的统一异常处理机制处理
662
880
  raise
663
881
 
664
- for i in range(int(start), int(end)):
665
- # 存储在本地变量字典和测试上下文中
666
- self.variable_replacer.local_variables[var_name] = i
667
- self.test_context.set(var_name, i)
668
- with allure.step(f"循环轮次: {var_name} = {i}"):
669
- try:
670
- self.execute(node.children[2])
671
- except BreakException:
672
- # 遇到break语句,退出循环
673
- allure.attach(
674
- f"在 {var_name} = {i} 时遇到break语句,退出循环",
675
- name="循环Break",
676
- attachment_type=allure.attachment_type.TEXT
677
- )
678
- break
679
- except ContinueException:
680
- # 遇到continue语句,跳过本次循环
681
- allure.attach(
682
- f"在 {var_name} = {i} 时遇到continue语句,跳过本次循环",
683
- name="循环Continue",
684
- attachment_type=allure.attachment_type.TEXT
685
- )
686
- continue
687
- except ReturnException as e:
688
- # 遇到return语句,将异常向上传递
689
- allure.attach(
690
- f"在 {var_name} = {i} 时遇到return语句,退出函数",
691
- name="循环Return",
692
- attachment_type=allure.attachment_type.TEXT
693
- )
694
- raise e
695
-
696
882
  def _execute_keyword_call(self, node):
697
883
  """执行关键字调用"""
698
884
  keyword_name = node.value
699
- line_info = (f"\n行号: {node.line_number}"
700
- if hasattr(node, 'line_number') and node.line_number
701
- else "")
885
+ line_info = self._get_line_info(node)
702
886
 
703
887
  # 先检查关键字是否存在
704
888
  keyword_info = keyword_manager.get_keyword_info(keyword_name)
705
889
  if not keyword_info:
706
890
  error_msg = f"未注册的关键字: {keyword_name}"
707
- allure.attach(
708
- f"关键字: {keyword_name}\n错误: {error_msg}{line_info}",
709
- name="关键字调用失败",
710
- attachment_type=allure.attachment_type.TEXT
711
- )
891
+ # 在步骤内部记录异常
892
+ with allure.step(f"调用关键字: {keyword_name}"):
893
+ allure.attach(
894
+ f"执行KeywordCall节点: 未注册的关键字: {keyword_name}{line_info}\n上下文: 执行KeywordCall节点",
895
+ name="DSL执行异常",
896
+ attachment_type=allure.attachment_type.TEXT
897
+ )
712
898
  raise Exception(error_msg)
713
899
 
714
- kwargs = self._prepare_keyword_params(node, keyword_info)
715
900
  step_name = f"调用关键字: {keyword_name}"
716
901
 
717
902
  with allure.step(step_name):
718
903
  try:
904
+ # 准备参数(这里可能抛出参数解析异常)
905
+ kwargs = self._prepare_keyword_params(node, keyword_info)
906
+
719
907
  # 传递自定义步骤名称给KeywordManager,避免重复的allure步骤嵌套
720
908
  kwargs['step_name'] = keyword_name # 内层步骤只显示关键字名称
721
909
  # 避免KeywordManager重复记录,由DSL执行器统一记录
@@ -732,44 +920,62 @@ class DSLExecutor:
732
920
 
733
921
  return result
734
922
  except Exception as e:
735
- # 记录关键字执行失败,包含行号信息和更详细的错误信息
736
- error_details = (
737
- f"关键字: {keyword_name}\n"
738
- f"错误: {str(e)}\n"
739
- f"错误类型: {type(e).__name__}"
740
- f"{line_info}"
741
- )
742
-
743
- # 如果异常中包含了内部行号信息,提取并显示
744
- if hasattr(e, 'args') and e.args:
745
- error_msg = str(e.args[0])
746
- # 尝试从错误消息中提取行号信息
747
- import re
748
- line_match = re.search(r'行(\d+)', error_msg)
749
- if line_match:
750
- inner_line = int(line_match.group(1))
751
- error_details += f"\n内部错误行号: {inner_line}"
923
+ # 统一在关键字调用层级记录异常,包含行号信息
924
+ if "参数解析异常" in str(e) or "无法解析变量引用" in str(e):
925
+ # 参数解析异常,提取核心错误信息
926
+ core_error = str(e)
927
+ if "参数解析异常" in core_error:
928
+ # 提取参数名和具体错误
929
+ import re
930
+ match = re.search(r'参数解析异常 \(([^)]+)\): (.+)', core_error)
931
+ if match:
932
+ param_name, detailed_error = match.groups()
933
+ error_details = f"参数解析失败 ({param_name}): {detailed_error}{line_info}\n上下文: 执行KeywordCall节点"
934
+ else:
935
+ error_details = f"参数解析失败: {core_error}{line_info}\n上下文: 执行KeywordCall节点"
936
+ else:
937
+ error_details = f"参数解析失败: {core_error}{line_info}\n上下文: 执行KeywordCall节点"
938
+ else:
939
+ # 其他异常
940
+ error_details = f"执行KeywordCall节点: {str(e)}{line_info}\n上下文: 执行KeywordCall节点"
752
941
 
753
942
  allure.attach(
754
943
  error_details,
755
- name="关键字调用失败",
944
+ name="DSL执行异常",
756
945
  attachment_type=allure.attachment_type.TEXT
757
946
  )
947
+ # 重新抛出异常,让外层的统一异常处理机制处理
758
948
  raise
759
949
 
760
950
  def _prepare_keyword_params(self, node, keyword_info):
761
951
  """准备关键字调用参数"""
762
952
  mapping = keyword_info.get('mapping', {})
763
953
  kwargs = {'context': self.test_context} # 默认传入context参数
954
+ line_info = self._get_line_info(node)
764
955
 
765
956
  # 检查是否有参数列表
766
957
  if node.children[0]:
767
958
  for param in node.children[0]:
768
959
  param_name = param.value
769
960
  english_param_name = mapping.get(param_name, param_name)
770
- # 对参数值进行变量替换
771
- param_value = self.eval_expression(param.children[0])
772
- kwargs[english_param_name] = param_value
961
+
962
+ # 在子步骤中处理参数值解析,但不记录异常详情
963
+ with allure.step(f"解析参数: {param_name}"):
964
+ try:
965
+ # 对参数值进行变量替换
966
+ param_value = self.eval_expression(param.children[0])
967
+ kwargs[english_param_name] = param_value
968
+
969
+ # 只记录参数解析成功的简要信息
970
+ allure.attach(
971
+ f"参数名: {param_name}\n"
972
+ f"参数值: {param_value}",
973
+ name="参数解析详情",
974
+ attachment_type=allure.attachment_type.TEXT
975
+ )
976
+ except Exception as e:
977
+ # 将异常重新包装,添加参数名信息,但不在这里记录到allure
978
+ raise Exception(f"参数解析异常 ({param_name}): {str(e)}")
773
979
 
774
980
  return kwargs
775
981
 
@@ -868,26 +1074,24 @@ class DSLExecutor:
868
1074
  call_info = node.value
869
1075
  alias = call_info['alias']
870
1076
  keyword_name = call_info['keyword']
871
- line_info = (f"\n行号: {node.line_number}"
872
- if hasattr(node, 'line_number') and node.line_number
873
- else "")
1077
+ line_info = self._get_line_info(node)
874
1078
 
875
- # 准备参数
876
- params = []
877
- if node.children and node.children[0]:
878
- params = node.children[0]
1079
+ with allure.step(f"执行远程关键字: {alias}|{keyword_name}"):
1080
+ try:
1081
+ # 准备参数
1082
+ params = []
1083
+ if node.children and node.children[0]:
1084
+ params = node.children[0]
879
1085
 
880
- kwargs = {}
881
- for param in params:
882
- param_name = param.value
883
- param_value = self.eval_expression(param.children[0])
884
- kwargs[param_name] = param_value
1086
+ kwargs = {}
1087
+ for param in params:
1088
+ param_name = param.value
1089
+ param_value = self.eval_expression(param.children[0])
1090
+ kwargs[param_name] = param_value
885
1091
 
886
- # 添加测试上下文
887
- kwargs['context'] = self.test_context
1092
+ # 添加测试上下文
1093
+ kwargs['context'] = self.test_context
888
1094
 
889
- with allure.step(f"执行远程关键字: {alias}|{keyword_name}"):
890
- try:
891
1095
  # 执行远程关键字
892
1096
  result = remote_keyword_manager.execute_remote_keyword(
893
1097
  alias, keyword_name, **kwargs)
@@ -899,12 +1103,14 @@ class DSLExecutor:
899
1103
  )
900
1104
  return result
901
1105
  except Exception as e:
902
- # 记录错误并重新抛出
1106
+ # 在步骤内部记录异常详情
1107
+ error_details = f"执行RemoteKeywordCall节点: {str(e)}{line_info}\n上下文: 执行RemoteKeywordCall节点"
903
1108
  allure.attach(
904
- f"远程关键字执行失败: {str(e)}{line_info}",
905
- name="远程关键字错误",
1109
+ error_details,
1110
+ name="DSL执行异常",
906
1111
  attachment_type=allure.attachment_type.TEXT
907
1112
  )
1113
+ # 重新抛出异常,让外层的统一异常处理机制处理
908
1114
  raise
909
1115
 
910
1116
  def _handle_assignment_remote_keyword_call(self, node):
@@ -914,74 +1120,69 @@ class DSLExecutor:
914
1120
  node: AssignmentRemoteKeywordCall节点
915
1121
  """
916
1122
  var_name = node.value
917
- line_info = (f"\n行号: {node.line_number}"
918
- if hasattr(node, 'line_number') and node.line_number
919
- else "")
1123
+ line_info = self._get_line_info(node)
920
1124
 
921
- try:
922
- remote_keyword_call_node = node.children[0]
923
- result = self.execute(remote_keyword_call_node)
924
-
925
- if result is not None:
926
- # 注意:远程关键字客户端已经处理了新格式的返回值,
927
- # 这里接收到的result应该已经是主要返回值,而不是完整的字典格式
928
- # 但为了保险起见,我们仍然检查是否为新格式
929
- if isinstance(result, dict) and 'result' in result:
930
- # 如果仍然是新格式(可能是嵌套的远程调用),提取主要返回值
931
- main_result = result['result']
932
-
933
- # 处理captures字段中的变量
934
- captures = result.get('captures', {})
935
- for capture_var, capture_value in captures.items():
936
- if capture_var.startswith('g_'):
937
- global_context.set_variable(
938
- capture_var, capture_value)
939
- else:
940
- self.variable_replacer.local_variables[
941
- capture_var] = capture_value
942
- self.test_context.set(capture_var, capture_value)
1125
+ with allure.step(f"远程关键字赋值: {var_name}"):
1126
+ try:
1127
+ remote_keyword_call_node = node.children[0]
1128
+ result = self.execute(remote_keyword_call_node)
943
1129
 
944
- # 将主要结果赋值给指定变量
945
- actual_result = main_result
946
- else:
947
- # 传统格式或已经处理过的格式,直接使用结果
948
- actual_result = result
1130
+ if result is not None:
1131
+ # 注意:远程关键字客户端已经处理了新格式的返回值,
1132
+ # 这里接收到的result应该已经是主要返回值,而不是完整的字典格式
1133
+ # 但为了保险起见,我们仍然检查是否为新格式
1134
+ if isinstance(result, dict) and 'result' in result:
1135
+ # 如果仍然是新格式(可能是嵌套的远程调用),提取主要返回值
1136
+ main_result = result['result']
949
1137
 
950
- # 检查变量名是否以g_开头,如果是则设置为全局变量
951
- if var_name.startswith('g_'):
952
- global_context.set_variable(var_name, actual_result)
953
- allure.attach(
954
- f"全局变量: {var_name}\n值: {actual_result}{line_info}",
955
- name="远程关键字赋值",
956
- attachment_type=allure.attachment_type.TEXT
957
- )
1138
+ # 处理captures字段中的变量
1139
+ captures = result.get('captures', {})
1140
+ for capture_var, capture_value in captures.items():
1141
+ if capture_var.startswith('g_'):
1142
+ global_context.set_variable(
1143
+ capture_var, capture_value)
1144
+ else:
1145
+ self.variable_replacer.local_variables[
1146
+ capture_var] = capture_value
1147
+ self.test_context.set(capture_var, capture_value)
1148
+
1149
+ # 将主要结果赋值给指定变量
1150
+ actual_result = main_result
1151
+ else:
1152
+ # 传统格式,直接使用结果
1153
+ actual_result = result
1154
+
1155
+ # 检查变量名是否以g_开头,如果是则设置为全局变量
1156
+ if var_name.startswith('g_'):
1157
+ global_context.set_variable(var_name, actual_result)
1158
+ allure.attach(
1159
+ f"全局变量: {var_name}\n值: {actual_result}{line_info}",
1160
+ name="远程关键字赋值",
1161
+ attachment_type=allure.attachment_type.TEXT
1162
+ )
1163
+ else:
1164
+ # 存储在本地变量字典和测试上下文中
1165
+ self.variable_replacer.local_variables[
1166
+ var_name] = actual_result
1167
+ self.test_context.set(var_name, actual_result)
1168
+ allure.attach(
1169
+ f"变量: {var_name}\n值: {actual_result}{line_info}",
1170
+ name="远程关键字赋值",
1171
+ attachment_type=allure.attachment_type.TEXT
1172
+ )
958
1173
  else:
959
- # 存储在本地变量字典和测试上下文中
960
- self.variable_replacer.local_variables[
961
- var_name] = actual_result
962
- self.test_context.set(var_name, actual_result)
963
- allure.attach(
964
- f"变量: {var_name}\n值: {actual_result}{line_info}",
965
- name="远程关键字赋值",
966
- attachment_type=allure.attachment_type.TEXT
967
- )
968
- else:
969
- error_msg = "远程关键字没有返回结果"
970
- allure.attach(
971
- f"变量: {var_name}\n错误: {error_msg}{line_info}",
972
- name="远程关键字赋值失败",
973
- attachment_type=allure.attachment_type.TEXT
974
- )
975
- raise Exception(error_msg)
976
- except Exception as e:
977
- # 如果异常不是我们刚才抛出的,记录异常信息
978
- if "没有返回结果" not in str(e):
1174
+ error_msg = "远程关键字没有返回结果"
1175
+ raise Exception(error_msg)
1176
+ except Exception as e:
1177
+ # 在步骤内部记录异常详情
1178
+ error_details = f"执行AssignmentRemoteKeywordCall节点: {str(e)}{line_info}\n上下文: 执行AssignmentRemoteKeywordCall节点"
979
1179
  allure.attach(
980
- f"变量: {var_name}\n错误: {str(e)}{line_info}",
981
- name="远程关键字赋值失败",
1180
+ error_details,
1181
+ name="DSL执行异常",
982
1182
  attachment_type=allure.attachment_type.TEXT
983
1183
  )
984
- raise
1184
+ # 重新抛出异常,让外层的统一异常处理机制处理
1185
+ raise
985
1186
 
986
1187
  def execute(self, node):
987
1188
  """执行AST节点"""
@@ -1018,7 +1219,20 @@ class DSLExecutor:
1018
1219
  error_msg = f"未知的节点类型: {node.type}"
1019
1220
  if self.enable_tracking and self.execution_tracker:
1020
1221
  self.execution_tracker.finish_current_step(error=error_msg)
1021
- raise Exception(error_msg)
1222
+ # 使用统一的异常处理机制
1223
+ self._handle_exception_with_line_info(
1224
+ Exception(error_msg), node, f"执行节点 {node.type}")
1225
+
1226
+ # 管理节点栈 - 将有行号的节点推入栈
1227
+ if hasattr(node, 'line_number') and node.line_number:
1228
+ self._node_stack.append(node)
1229
+ stack_pushed = True
1230
+ else:
1231
+ stack_pushed = False
1232
+
1233
+ # 设置当前节点
1234
+ old_node = self._current_node
1235
+ self._current_node = node
1022
1236
 
1023
1237
  try:
1024
1238
  result = handler(node)
@@ -1033,7 +1247,36 @@ class DSLExecutor:
1033
1247
  if hasattr(node, 'line_number') and node.line_number:
1034
1248
  error_msg += f" (行{node.line_number})"
1035
1249
  self.execution_tracker.finish_current_step(error=error_msg)
1036
- raise
1250
+
1251
+ # 如果是控制流异常或已经是DSLExecutionError,直接重抛
1252
+ if isinstance(e, (BreakException, ContinueException, ReturnException, DSLExecutionError)):
1253
+ raise
1254
+
1255
+ # 如果是断言异常,保持原样但可能添加行号信息
1256
+ if isinstance(e, AssertionError):
1257
+ # 检查是否已经包含行号信息
1258
+ if not ("行号:" in str(e) or "行" in str(e)):
1259
+ line_info = self._get_line_info(node)
1260
+ if line_info:
1261
+ enhanced_msg = f"{str(e)}{line_info}"
1262
+ raise AssertionError(enhanced_msg) from e
1263
+ raise
1264
+
1265
+ # 其他异常使用统一处理机制
1266
+ # 对于这些节点类型,异常已经在步骤中记录过了,跳过重复记录
1267
+ step_handled_nodes = {
1268
+ 'KeywordCall', 'Assignment', 'AssignmentKeywordCall',
1269
+ 'ForLoop', 'RemoteKeywordCall', 'AssignmentRemoteKeywordCall'
1270
+ }
1271
+ skip_logging = node.type in step_handled_nodes
1272
+ self._handle_exception_with_line_info(
1273
+ e, node, f"执行{node.type}节点", skip_allure_logging=skip_logging)
1274
+ finally:
1275
+ # 恢复之前的节点
1276
+ self._current_node = old_node
1277
+ # 从栈中弹出节点
1278
+ if stack_pushed:
1279
+ self._node_stack.pop()
1037
1280
 
1038
1281
  def _get_remote_keyword_description(self, node):
1039
1282
  """获取远程关键字调用的描述"""
pytest_dsl/core/parser.py CHANGED
@@ -198,9 +198,27 @@ def p_expr_atom(p):
198
198
  elif isinstance(p[1], Node):
199
199
  p[0] = p[1]
200
200
  else:
201
- # 为基本表达式设置行号信息
201
+ # 为基本表达式设置行号信息和类型信息
202
202
  expr_line = getattr(p.slice[1], 'lineno', None)
203
- expr_node = Node('Expression', value=p[1])
203
+
204
+ # 根据token类型创建不同的节点类型
205
+ token_type = p.slice[1].type
206
+ if token_type == 'STRING':
207
+ # 字符串字面量
208
+ expr_node = Node('StringLiteral', value=p[1])
209
+ elif token_type == 'NUMBER':
210
+ # 数字字面量
211
+ expr_node = Node('NumberLiteral', value=p[1])
212
+ elif token_type == 'ID':
213
+ # 变量引用
214
+ expr_node = Node('VariableRef', value=p[1])
215
+ elif token_type == 'PLACEHOLDER':
216
+ # 变量占位符 ${var}
217
+ expr_node = Node('PlaceholderRef', value=p[1])
218
+ else:
219
+ # 其他类型,保持原来的行为
220
+ expr_node = Node('Expression', value=p[1])
221
+
204
222
  if expr_line is not None:
205
223
  expr_node.set_position(expr_line)
206
224
  p[0] = expr_node
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-dsl
3
- Version: 0.15.1
3
+ Version: 0.15.2
4
4
  Summary: A DSL testing framework based on pytest
5
5
  Author: Chen Shuanglin
6
6
  License: MIT
@@ -9,7 +9,7 @@ pytest_dsl/core/auto_decorator.py,sha256=9Mga-GB4AzV5nkB6zpfaq8IuHa0KOH8LlFvnWyH
9
9
  pytest_dsl/core/auto_directory.py,sha256=egyTnVxtGs4P75EIivRauLRPJfN9aZpoGVvp_Ma72AM,2714
10
10
  pytest_dsl/core/context.py,sha256=fXpVQon666Lz_PCexjkWMBBr-Xcd1SkDMp2vHar6eIs,664
11
11
  pytest_dsl/core/custom_keyword_manager.py,sha256=F9aSbth4x4-5nHb0N5uG04CpgwGnQv4RDGeRKR2WuIk,16001
12
- pytest_dsl/core/dsl_executor.py,sha256=aDK7rXbE5xLg4DcQ5VMbexSBuh4YGMDrVqtXENFCbsw,48638
12
+ pytest_dsl/core/dsl_executor.py,sha256=G3Ad97REt1jWwRvp7LS2Nq7zy3S2ZtYKlJeAJANDstI,60200
13
13
  pytest_dsl/core/dsl_executor_utils.py,sha256=ZJLSYSsiKHqg53d3Bl--ZF8m9abd5kpqdevWl31KEZg,2276
14
14
  pytest_dsl/core/execution_tracker.py,sha256=Pwcxraxt_xkOouq32KBqola-OVfnbaomCoMTyUIqoN4,9476
15
15
  pytest_dsl/core/global_context.py,sha256=NcEcS2V61MT70tgAsGsFWQq0P3mKjtHQr1rgT3yTcyY,3535
@@ -23,7 +23,7 @@ pytest_dsl/core/keyword_loader.py,sha256=3GQ4w5Zf2XdkoJ85uYXh9YB93_8L8OAb7vvuKE3
23
23
  pytest_dsl/core/keyword_manager.py,sha256=5WZWwlYk74kGHh1T6WjCuVd8eelq2-UEXvDIe4U7rEI,7730
24
24
  pytest_dsl/core/keyword_utils.py,sha256=1zIKzbA8Lhifc97skzN4oJV-2Cljzf9aVSutwjU7LaA,19847
25
25
  pytest_dsl/core/lexer.py,sha256=o_EJIadfhgyCImI73Y9ybqlBE9AisgA6nOhxpXNlaMw,4648
26
- pytest_dsl/core/parser.py,sha256=LCyFNwrIO0dEup7HubnCkceAPqY7Qt5aoSKQddqG7Os,15898
26
+ pytest_dsl/core/parser.py,sha256=SvTQ4jgMSe3MITSu9PftraElPAzVaBbNPHMEk1H_lFY,16597
27
27
  pytest_dsl/core/parsetab.py,sha256=o4XbFKwpsi3fYmfI_F6u5NSM61Qp6gTx-Sfh1jDINxI,31767
28
28
  pytest_dsl/core/plugin_discovery.py,sha256=3pt3EXJ7EPF0rkUlyDZMVHkIiTy2vicdIIQJkrHXZjY,8305
29
29
  pytest_dsl/core/utils.py,sha256=q0AMdw5HO33JvlA3UJeQ7IXh1Z_8K1QlwiM1_yiITrU,5350
@@ -73,9 +73,9 @@ pytest_dsl/remote/keyword_client.py,sha256=BL80MOaLroUi0v-9sLtkJ55g1R0Iw9SE1k11I
73
73
  pytest_dsl/remote/keyword_server.py,sha256=vGIE3Bhh461xX_u1U-Cf5nrWL2GQFYdtQdcMWfFIYgE,22320
74
74
  pytest_dsl/remote/variable_bridge.py,sha256=dv-d3Gq9ttvvrXM1fdlLtoSOPB6vRp0_GBOwX4wvcy8,7121
75
75
  pytest_dsl/templates/keywords_report.html,sha256=7x84iq6hi08nf1iQ95jZ3izcAUPx6JFm0_8xS85CYws,31241
76
- pytest_dsl-0.15.1.dist-info/licenses/LICENSE,sha256=Rguy8cb9sYhK6cmrBdXvwh94rKVDh2tVZEWptsHIsVM,1071
77
- pytest_dsl-0.15.1.dist-info/METADATA,sha256=tBZyIhox0tvZCCVK2aaP2EIUNyawij9coLH0NFn8PUo,29655
78
- pytest_dsl-0.15.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
- pytest_dsl-0.15.1.dist-info/entry_points.txt,sha256=PLOBbH02OGY1XR1JDKIZB1Em87loUvbgMRWaag-5FhY,204
80
- pytest_dsl-0.15.1.dist-info/top_level.txt,sha256=4CrSx4uNqxj7NvK6k1y2JZrSrJSzi-UvPZdqpUhumWM,11
81
- pytest_dsl-0.15.1.dist-info/RECORD,,
76
+ pytest_dsl-0.15.2.dist-info/licenses/LICENSE,sha256=Rguy8cb9sYhK6cmrBdXvwh94rKVDh2tVZEWptsHIsVM,1071
77
+ pytest_dsl-0.15.2.dist-info/METADATA,sha256=vfUUteu2RO84-TI0j0TngY18o_L1w1VTa2v94eX_XZc,29655
78
+ pytest_dsl-0.15.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
79
+ pytest_dsl-0.15.2.dist-info/entry_points.txt,sha256=PLOBbH02OGY1XR1JDKIZB1Em87loUvbgMRWaag-5FhY,204
80
+ pytest_dsl-0.15.2.dist-info/top_level.txt,sha256=4CrSx4uNqxj7NvK6k1y2JZrSrJSzi-UvPZdqpUhumWM,11
81
+ pytest_dsl-0.15.2.dist-info/RECORD,,