refactor: 重构执行引擎为上下文驱动架构
- 优化 WorkflowExecutor 与 Exchange支持 ExecutionEnv 资源注入。 - 实现 Session 级别连接复用与变量池内存镜像化,消除重复 I/O 开销。 - 引入 ChainMap 实现动态上下文切换,解决参数化变量与全局提取变量的优先级覆盖。 - 完善变量提取与断言逻辑,确保跨用例变量流转的可靠性。
This commit is contained in:
@@ -12,8 +12,7 @@ from typing import Any, Union, TypeVar
|
||||
import jsonpath
|
||||
from lxml import etree
|
||||
|
||||
|
||||
from core.models import CaseInfo
|
||||
from core.models import RawSchema
|
||||
from core.settings import EXTRACT_CACHE
|
||||
from core.templates import Template
|
||||
from commons.file_processors.yaml_processor import YamlProcessor
|
||||
@@ -25,16 +24,20 @@ T = TypeVar("T", bound=Union[dict, list, str, Any])
|
||||
|
||||
|
||||
class Exchange:
|
||||
def __init__(self, cache_path: str):
|
||||
self.cache_path = cache_path
|
||||
self.file_handler = YamlProcessor(filepath=self.cache_path)
|
||||
# 1. 增加内存缓存,避免频繁磁盘 I/O
|
||||
self._variable_cache = self.file_handler.load() or {}
|
||||
def __init__(self, variable_cache: dict[str, Any]):
|
||||
self._cache = variable_cache
|
||||
# 匹配标准变量 ${var},排除函数调用 ${func()}
|
||||
# self.var_only_pattern = re.compile(r"\$\{([a-zA-Z_]\w*)}")
|
||||
self.var_only_pattern = re.compile(r"^\$\{([a-zA-Z_]\w*)}$")
|
||||
|
||||
def extract(self, resp, var_name: str, attr: str, expr: str, index: int = 0):
|
||||
@property
|
||||
def global_vars(self) -> dict:
|
||||
return self._cache
|
||||
|
||||
@global_vars.setter
|
||||
def global_vars(self, global_vars: dict) -> None:
|
||||
self._cache = global_vars
|
||||
|
||||
def extract(self, resp: Any, var_name: str, attr: str, expr: str, index: int = 0):
|
||||
"""
|
||||
从响应中提取数据并更新到缓存及文件
|
||||
:param resp: Response 对象
|
||||
@@ -63,8 +66,12 @@ class Exchange:
|
||||
res = jsonpath.jsonpath(target_data, expr)
|
||||
if res: value = res[index]
|
||||
elif expr.startswith("/") or expr.startswith("./"): # XPath 模式
|
||||
html_content = getattr(resp, "text", "") # 使用 getattr 防护
|
||||
if not html_content:
|
||||
logger.warning("XPath 提取失败:响应文本为空")
|
||||
return
|
||||
# 将文本解析为 HTML 树
|
||||
html_content = resp.text
|
||||
# html_content = resp.text
|
||||
tree = etree.HTML(html_content)
|
||||
res = tree.xpath(expr)
|
||||
if res:
|
||||
@@ -79,8 +86,7 @@ class Exchange:
|
||||
logger.warning(f"变量 [{var_name}] 未通过表达式 [{expr}] 提取到数据")
|
||||
value = "not data"
|
||||
|
||||
self._variable_cache[var_name] = value
|
||||
self.file_handler.save(self._variable_cache)
|
||||
self._cache[var_name] = value
|
||||
logger.info(f"变量提取成功: {var_name} -> {value} (Type: {type(value).__name__})")
|
||||
|
||||
except Exception as e:
|
||||
@@ -103,13 +109,13 @@ class Exchange:
|
||||
if full_match:
|
||||
var_name = full_match.group(1)
|
||||
|
||||
return self._variable_cache.get(var_name, content)
|
||||
return self._cache.get(var_name, content)
|
||||
|
||||
# B. 场景:混合文本或函数调用
|
||||
# 例子:"Bearer ${token}" 或 "${gen_phone()}"
|
||||
if "${" in content:
|
||||
# 调用你提供的 Template 类
|
||||
return Template(content).render(self._variable_cache)
|
||||
return Template(content).render(self._cache)
|
||||
|
||||
return content
|
||||
|
||||
@@ -129,13 +135,12 @@ class Exchange:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from core.models import CaseInfo, RequestModel
|
||||
from core.models import RawSchema, HttpAction
|
||||
|
||||
# 模拟外部写入一个初始变量
|
||||
with open(EXTRACT_CACHE, "w") as f:
|
||||
f.write("existing_var: '100'\n")
|
||||
file_handler = YamlProcessor(filepath=EXTRACT_CACHE)
|
||||
variable_cache_ = file_handler.load() or {}
|
||||
|
||||
ex = Exchange(EXTRACT_CACHE)
|
||||
ex = Exchange(variable_cache_)
|
||||
|
||||
|
||||
# --- 场景 1: 变量提取验证 ---
|
||||
@@ -157,7 +162,7 @@ if __name__ == "__main__":
|
||||
# 定义一个复杂的 CaseInfo
|
||||
raw_case = {
|
||||
"title": "测试用例",
|
||||
"request": {
|
||||
"action": {
|
||||
"method": "POST",
|
||||
"url": "http://api.com/${token}", # 混合文本 -> 应转为 str
|
||||
"json_body": {
|
||||
@@ -170,20 +175,21 @@ if __name__ == "__main__":
|
||||
}
|
||||
|
||||
print("\n>>> 执行替换...")
|
||||
new_case = ex.replace(raw_case)
|
||||
print(new_case)
|
||||
new_case = CaseInfo(**new_case)
|
||||
|
||||
# --- 校验结果 ---
|
||||
new_case_one = ex.replace(raw_case)
|
||||
print(new_case_one)
|
||||
RawSchema(**new_case_one)
|
||||
print(new_case_one.get("action"))
|
||||
action = HttpAction(**new_case_one.get("action"))
|
||||
print(action)
|
||||
# # --- 校验结果 ---
|
||||
print("\n--- 验证结果 ---")
|
||||
print(f"URL (混合文本): {new_case.request.url} | 类型: {type(new_case.request.url)}")
|
||||
print(f"ID (类型保持): {new_case.request.json_body['id']} | 类型: {type(new_case.request.json_body['id'])}")
|
||||
print(f"Timeout (自动转换): {new_case.request.timeout} | 类型: {type(new_case.request.timeout)}")
|
||||
print(f"URL (混合文本): {action.url} | 类型: {type(action.url)}")
|
||||
print(f"ID (类型保持): {action.json_body['id']} | 类型: {type(action.json_body['id'])}")
|
||||
print(f"Timeout (自动转换): {action.timeout} | 类型: {type(action.timeout)}")
|
||||
# #
|
||||
assert isinstance(action.json_body['id'], int)
|
||||
# #
|
||||
assert action.url == "http://api.com/auth_123"
|
||||
assert action.timeout == 100
|
||||
|
||||
assert isinstance(new_case.request.json_body['id'], int)
|
||||
|
||||
assert new_case.request.url == "http://api.com/auth_123"
|
||||
assert new_case.request.timeout == 100
|
||||
|
||||
# if os.path.exists(cache_path): os.remove(cache_path)
|
||||
print("\nExchange 场景全部验证通过!")
|
||||
|
||||
Reference in New Issue
Block a user