refactor: 重构执行引擎为上下文驱动架构
- 优化 WorkflowExecutor 与 Exchange支持 ExecutionEnv 资源注入。 - 实现 Session 级别连接复用与变量池内存镜像化,消除重复 I/O 开销。 - 引入 ChainMap 实现动态上下文切换,解决参数化变量与全局提取变量的优先级覆盖。 - 完善变量提取与断言逻辑,确保跨用例变量流转的可靠性。
This commit is contained in:
@@ -8,11 +8,12 @@
|
||||
import logging
|
||||
import importlib
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from collections import ChainMap
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from core import settings
|
||||
from core.models import CaseInfo, ValidateItem, RequestModel, ApiActionModel
|
||||
from core.context import ExecutionEnv
|
||||
from core.models import RawSchema, ValidateItem, HttpAction, ApiActionModel
|
||||
from core.session import Session
|
||||
from core.exchange import Exchange
|
||||
from utils.case_validator import CaseValidator
|
||||
@@ -24,61 +25,54 @@ VALIDATE_LIST_ADAPTER = TypeAdapter(List[ValidateItem])
|
||||
|
||||
|
||||
class WorkflowExecutor:
|
||||
def __init__(self, session: Session, exchanger: Exchange):
|
||||
self.session = session
|
||||
self.exchanger = exchanger
|
||||
|
||||
def perform(self, case_info: CaseInfo,context: Optional[dict[str, Any]] = None) -> Any:
|
||||
@classmethod
|
||||
def perform(cls, case_info: RawSchema, env: ExecutionEnv, context: Optional[dict[str, Any]] = None) -> Any:
|
||||
"""执行单个用例:支持直接请求和PO模式调用"""
|
||||
context = context or {}
|
||||
# --- 重点 1:备份并切换上下文 ---
|
||||
# 保存 Exchange 当前的全局字典引用
|
||||
original_cache = env.exchanger.global_vars
|
||||
# 1. 建立优先级变量池 (参数化变量 > 全局提取变量)
|
||||
# ChainMap 是实现“局部覆盖全局”性能最好的方案
|
||||
combined_vars = ChainMap(context, original_cache)
|
||||
# 将 Exchange 的内部缓存临时指向这个合并池
|
||||
env.exchanger.global_vars = combined_vars
|
||||
|
||||
# 1. 局部变量优先级注入
|
||||
# 备份全局缓存,将当前行数据合并进去
|
||||
old_cache = self.exchanger._variable_cache.copy()
|
||||
self.exchanger._variable_cache.update(context)
|
||||
|
||||
resp = None # 初始化 resp,避免异常时引用未定义
|
||||
try:
|
||||
# 2. 动态更新标题(如果 context 中包含 title)
|
||||
current_title = context.get("title") or case_info.title
|
||||
logger.info(f"🚀 执行用例: {current_title}")
|
||||
|
||||
# raw_data = case_info.model_dump(by_alias=True, exclude_none=True)
|
||||
# 1. 变量替换(将 ${var} 替换为真实值)
|
||||
# rendered_dict = self.exchanger.replace(raw_data)
|
||||
# rendered_case = CaseInfo.model_validate(rendered_dict)
|
||||
raw_action_dict = case_info.action.model_dump(by_alias=True, exclude_none=True)
|
||||
rendered_action_dict = env.exchanger.replace(raw_action_dict)
|
||||
|
||||
# --- 2. 决定执行模式 ---
|
||||
if case_info.is_po_mode():
|
||||
# PO 模式:仅渲染 api_action
|
||||
action_dict = case_info.api_action.model_dump(by_alias=True, exclude_none=True)
|
||||
rendered_action_dict = self.exchanger.replace(action_dict)
|
||||
# 重新校验以修复类型(如 params 里的 int)
|
||||
rendered_action = ApiActionModel.model_validate(rendered_action_dict)
|
||||
# PO 模式:反射调用
|
||||
|
||||
resp = self._execute_po_method(action=rendered_action)
|
||||
resp = cls._execute_po_method(rendered_action, env)
|
||||
else:
|
||||
# 接口模式:直接请求
|
||||
# 直接将 RequestModel 转为字典传给 session.request
|
||||
request_kwargs = case_info.request.model_dump(by_alias=True, exclude_none=True)
|
||||
rendered_req_dict = self.exchanger.replace(request_kwargs)
|
||||
rendered_request = RequestModel.model_validate(rendered_req_dict)
|
||||
|
||||
rendered_request = HttpAction.model_validate(rendered_action_dict)
|
||||
request_kwargs = rendered_request.model_dump(by_alias=True, exclude_none=True)
|
||||
resp = self.session.request(**request_kwargs)
|
||||
resp = env.session.request(**request_kwargs)
|
||||
|
||||
# --- 3. 后置处理 (提取 & 断言) ---
|
||||
self._post_process(resp, case_info)
|
||||
# --- 3. 后处理:提取与断言 ---
|
||||
cls._post_process(resp, case_info, env, original_cache)
|
||||
|
||||
return resp
|
||||
except Exception as e:
|
||||
logger.error(f"用例执行失败: {case_info.title} | 原因: {e}", exc_info=True)
|
||||
raise
|
||||
finally:
|
||||
# 4. 关键:清理现场,还原全局变量池
|
||||
self.exchanger._variable_cache = old_cache
|
||||
# 兜底确保环境还原 (尽管 try 块中已经还原了一次,这里确保异常情况下也复位)
|
||||
env.exchanger.global_vars = original_cache
|
||||
|
||||
|
||||
def _execute_po_method(self, action: ApiActionModel):
|
||||
@staticmethod
|
||||
def _execute_po_method(action: ApiActionModel, env: ExecutionEnv):
|
||||
"""核心反射逻辑:根据字符串动态加载 api/ 目录下的类并执行方法"""
|
||||
class_name = action.api_class
|
||||
method_name = action.method
|
||||
@@ -94,15 +88,13 @@ class WorkflowExecutor:
|
||||
|
||||
try:
|
||||
# 1. 动态导入模块(假设都在 api 目录下)
|
||||
# 例如 class_name 是 UserAPI,则尝试从 api.user 导入
|
||||
# 这里简单处理,你可以根据你的文件名约定进一步优化逻辑
|
||||
# module_name = f"api.{class_name.lower().replace('api', '')}"
|
||||
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
# 2. 获取类并实例化
|
||||
cls = getattr(module, class_name)
|
||||
api_instance = cls(self.session) # 传入 session 保持会话统一
|
||||
|
||||
api_instance = cls(env.session) # 传入 session 保持会话统一
|
||||
|
||||
# 3. 调用方法并返回结果
|
||||
method = getattr(api_instance, method_name)
|
||||
@@ -118,21 +110,33 @@ class WorkflowExecutor:
|
||||
logger.error(f"反射调用失败: {class_name}.{method_name} -> {e}")
|
||||
raise
|
||||
|
||||
def _post_process(self, resp: Any, rendered_case: CaseInfo):
|
||||
# 3. 提取变量 (接口关联)
|
||||
if rendered_case.extract:
|
||||
for var_name, extract_info in rendered_case.extract.items():
|
||||
self.exchanger.extract(resp, var_name, *extract_info)
|
||||
@classmethod
|
||||
def _post_process(cls, resp: Any, case_info: RawSchema, env: ExecutionEnv, original_cache: dict):
|
||||
"""
|
||||
统一后处理逻辑:处理变量提取(写全局)和断言校验(读局部+全局)
|
||||
"""
|
||||
# 记录当前的混合上下文 (ChainMap),供断言使用
|
||||
combined_vars = env.exchanger.global_vars
|
||||
|
||||
# 4. 断言校验
|
||||
if rendered_case.validate_data:
|
||||
# raw_validate_list = [i.model_dump(by_alias=True) for i in rendered_case.validate_data]
|
||||
# 1. 变量提取 (Write Operation)
|
||||
if case_info.extract:
|
||||
try:
|
||||
# 必须切回 original_cache 才能持久化写入到全局变量池
|
||||
env.exchanger.global_vars = original_cache
|
||||
for var_name, extract_info in case_info.extract.items():
|
||||
env.exchanger.extract(resp, var_name, *extract_info)
|
||||
finally:
|
||||
# 提取完成后,切回 combined_vars,防止后续逻辑(如断言)丢失局部变量上下文
|
||||
env.exchanger.global_vars = combined_vars
|
||||
|
||||
# 2. 断言校验 (Read Operation)
|
||||
if case_info.validate_data:
|
||||
raw_validate_list = [
|
||||
item.model_dump(by_alias=True) if isinstance(item, ValidateItem) else item
|
||||
for item in rendered_case.validate_data
|
||||
for item in case_info.validate_data
|
||||
]
|
||||
|
||||
rendered_validate_list = self.exchanger.replace(raw_validate_list)
|
||||
rendered_validate_list = env.exchanger.replace(raw_validate_list)
|
||||
# 重新通过 Adapter 触发类型修复 (str -> int)
|
||||
final_validate_data = VALIDATE_LIST_ADAPTER.validate_python(rendered_validate_list)
|
||||
CaseValidator.validate(resp, final_validate_data)
|
||||
|
||||
Reference in New Issue
Block a user