Skip to content

Commit

Permalink
update gconfig replace way
Browse files Browse the repository at this point in the history
  • Loading branch information
wuranxu committed Jan 21, 2024
1 parent 0b79286 commit b00fc04
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 51 deletions.
4 changes: 1 addition & 3 deletions app/core/constructor/case_constructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ async def run(executor, env, index, path, params, req_params, constructor: Const
case_id = data.get("constructor_case_id")
if not case_id:
raise Exception("未获取到前/后置条件的用例id, 请检查前置条件")
testcase, err = await TestCaseDao.async_query_test_case(case_id)
if err:
raise Exception(f"用例: [{case_id}]不存在:")
testcase = await TestCaseDao.async_query_test_case(case_id)
executor.append(f"当前路径: {path}, 第{index + 1}{ConstructorAbstract.get_name(constructor)}")
# 说明是case
executor_class = kwargs.get('executor_class')(executor.logger)
Expand Down
112 changes: 68 additions & 44 deletions app/core/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import time
from collections import defaultdict
from datetime import datetime
from typing import List, Any
from typing import List, Any, Callable

from app.core.constructor.case_constructor import TestcaseConstructor
from app.core.constructor.http_constructor import HttpConstructor
Expand All @@ -14,6 +14,7 @@
from app.core.msg.dingtalk import DingTalk
from app.core.msg.mail import Email
from app.core.paramters import parameters_parser
from app.core.render import Render
from app.core.ws_connection_manager import ws_manage
from app.crud.auth.UserDao import UserDao
from app.crud.config.AddressDao import PityGatewayDao
Expand Down Expand Up @@ -45,15 +46,33 @@
from app.utils.logger import Log
from config import Config

# construct method mapping
construct_type = {
ConstructorType.testcase: TestcaseConstructor,
ConstructorType.sql: SqlConstructor,
ConstructorType.redis: RedisConstructor,
ConstructorType.py_script: PythonConstructor,
ConstructorType.http: HttpConstructor,
}

# gconfig parser mapping
gconfig_parser = {
GConfigParserEnum.string: StringGConfigParser.get_data,
GConfigParserEnum.json: JSONGConfigParser.get_data,
GConfigParserEnum.yaml: YamlGConfigParser.get_data,
}


class Executor(object):
log = Log("Executor")
el_exp = r"\$\{(.+?)\}"
pattern = re.compile(el_exp)
# 需要替换全局变量的字段
fields = ['body', 'url', 'request_headers']
fields = ['url', 'request_headers']

def __init__(self, log: CaseLog = None):
# 这里是一个彩蛋, 奔驰大G LB(括弧1.3T)
self.glb = None
if log is None:
self._logger = CaseLog()
self._main = True
Expand All @@ -67,17 +86,7 @@ def logger(self):

@staticmethod
def get_constructor_type(c: Constructor):
if c.type == ConstructorType.testcase:
return TestcaseConstructor
if c.type == ConstructorType.sql:
return SqlConstructor
if c.type == ConstructorType.redis:
return RedisConstructor
if c.type == ConstructorType.py_script:
return PythonConstructor
if c.type == ConstructorType.http:
return HttpConstructor
return None
return construct_type.get(c.type)

def append(self, content, end=False):
if end:
Expand All @@ -93,17 +102,36 @@ async def parse_gconfig(self, data, type_, env, *fields):
for f in fields:
await self.parse_field(data, f, GconfigType.text(type_), env)

async def parse_gconfig_v2(self, data, type_, *fields):
"""upgrade for replacing global variables"""
for f in fields:
self.append("解析{}: [{}]中的全局变量".format(GconfigType.text(type_), data, f))
origin_field = getattr(data, f)
# if not None or ""
if origin_field:
rendered = Render.render(self.glb, origin_field)
if rendered != origin_field:
self.append("替换全局变量成功, [{}]:\n\n[{}] -> [{}]\n".format(f, origin_field, rendered))
setattr(data, f, rendered)

@case_log
def get_parser(self, key_type):
async def query_gconfig(self, env: int):
"""加载全局变量"""
gconfig_list = await GConfigDao.list_gconfig(env)
gconfig_map = dict()
for g in gconfig_list:
parser = Executor.get_parser(g.key_type)
gconfig_map[g.key] = parser(g.value)
self.glb = gconfig_map

@staticmethod
def get_parser(key_type) -> Callable:
"""获取变量解析器
"""
if key_type == GConfigParserEnum.string:
return StringGConfigParser.parse
if key_type == GConfigParserEnum.json:
return JSONGConfigParser.parse
if key_type == GConfigParserEnum.yaml:
return YamlGConfigParser.parse
raise Exception(f"全局变量类型: {key_type}不合法, 请检查!")
parser = gconfig_parser.get(key_type)
if parser is None:
raise Exception(f"全局变量类型: {key_type}不合法, 请检查!")
return parser

async def parse_field(self, data, field, name, env):
"""
Expand Down Expand Up @@ -283,42 +311,45 @@ async def run(self, env: int, case_id: int, params_pool: dict = None, request_pa
if req_params is None:
req_params = dict()

# 加载全局变量
await self.query_gconfig(env)

# 挂载全局变量
case_params.update(self.glb)
request_param.update(self.glb)

try:
case_info, err = await TestCaseDao.async_query_test_case(case_id)
if err:
return response_info, err
case_info = await TestCaseDao.async_query_test_case(case_id)
response_info['case_id'] = case_info.id
response_info["case_name"] = case_info.name
method = case_info.request_method.upper()
response_info["request_method"] = method

# Step1: 替换全局变量
await self.parse_gconfig(case_info, GconfigType.case, env, *Executor.fields)

self.append("解析全局变量", True)

# Step2: 获取构造数据
constructors = await self.get_constructor(case_id)

# Step3: 解析前后置条件的全局变量
for c in constructors:
await self.parse_gconfig(c, GconfigType.constructor, env, "constructor_json")
# # Step3: 解析前后置条件的全局变量
# for c in constructors:
# await self.parse_gconfig_v2(c, GconfigType.constructor, "constructor_json")

# Step4: 获取断言
asserts = await TestCaseAssertsDao.async_list_test_case_asserts(case_id)

# 获取出参信息
out_parameters = await PityTestCaseOutParametersDao.select_list(case_id=case_id)

for ast in asserts:
await self.parse_gconfig(ast, GconfigType.asserts, env, "expected", "actually")
# for ast in asserts:
# await self.parse_gconfig_v2(ast, GconfigType.asserts, "expected", "actually")

# Step5: 替换参数
self.replace_args(req_params, case_info, constructors, asserts)

# Step6: 执行前置条件
await self.execute_constructors(env, path, case_info, case_params, req_params, constructors, asserts)

# 获取全局变量更新body url headers
await self.parse_gconfig_v2(case_info, GconfigType.case, *Executor.fields)

# Step7: 批量改写主方法参数
await self.parse_params(case_info, case_params)

Expand All @@ -338,6 +369,7 @@ async def run(self, env: int, case_id: int, params_pool: dict = None, request_pa
case_info.url = f"{base_path}{case_info.url}"

response_info["url"] = case_info.url
response_info["request_data"] = body

# Step9: 完成http请求
request_obj = await AsyncRequest.client(url=case_info.url, body_type=case_info.body_type, headers=headers,
Expand All @@ -356,7 +388,6 @@ async def run(self, env: int, case_id: int, params_pool: dict = None, request_pa

# 写入response
# TODO
# req_params["response"] = res.get("response", "")

self.replace_asserts(asserts, req_params, case_params)
self.replace_constructors(constructors, req_params, case_params)
Expand Down Expand Up @@ -472,17 +503,10 @@ async def run_single(env: int, data, report_id, case_id, params_pool: dict = Non
@case_log
def replace_body(self, req_params, body, body_type=1):
"""根据传入的构造参数进行参数替换"""
if body_type != BodyType.json:
self.append("当前请求数据不为json, 跳过替换")
return body
try:
if body:
data = json.loads(body)
if req_params is not None:
for k, v in req_params.items():
if data.get(k) is not None:
data[k] = v
return json.dumps(data, ensure_ascii=False)
# 2024-01-22 switch to Render
return Render.render(req_params, body)
self.append(f"body为空, 不进行替换")
except Exception as e:
self.append(f"替换请求body失败, {e}")
Expand Down
2 changes: 2 additions & 0 deletions app/core/render.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ def get_env():
if func.startswith("__"):
continue
my_env.globals[func] = getattr(PityFunction, func)
my_env.variable_start_string = "${"
my_env.variable_end_string = "}"
return my_env


Expand Down
16 changes: 16 additions & 0 deletions app/crud/config/GConfigDao.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import List

from sqlalchemy import select

from app.crud import Mapper, ModelWrapper
Expand Down Expand Up @@ -39,3 +41,17 @@ async def async_get_gconfig_by_key(key: str, env: int) -> GConfig:
return result.scalars().first()
except Exception as e:
raise Exception(f"查询全局变量失败: {str(e)}")

@staticmethod
async def list_gconfig(env: int) -> List[GConfig]:
"""
get available gconfig
"""
try:
filters = [GConfig.deleted_at == 0, GConfig.enable == True, GConfig.env == env]
async with async_session() as session:
sql = select(GConfig).where(*filters)
result = await session.execute(sql)
return result.scalars().all()
except Exception as e:
raise Exception(f"查询全局变量失败: {str(e)}")
6 changes: 3 additions & 3 deletions app/crud/test_case/TestCaseDao.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,11 @@ async def async_query_test_case(case_id) -> [TestCase, str]:
select(TestCase).where(TestCase.id == case_id, TestCase.deleted_at == 0))
data = result.scalars().first()
if data is None:
return None, "用例不存在"
return data, None
raise Exception(f"用例id: {case_id}不存在, 可能已经被删除")
return data
except Exception as e:
TestCaseDao.__log__.error(f"查询用例失败: {str(e)}")
return None, f"查询用例失败: {str(e)}"
raise Exception(f"查询用例失败: {str(e)}")

@classmethod
async def list_testcase_tree(cls, projects: List[Project]) -> [List, dict]:
Expand Down
10 changes: 9 additions & 1 deletion app/utils/gconfig_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ class GConfigParser(object):

@staticmethod
def parse(value, jsonpath):
pass
raise NotImplementedError

@staticmethod
def get_data(value):
raise NotImplementedError

@staticmethod
def get(data, key):
Expand Down Expand Up @@ -67,6 +71,10 @@ def parse(value, jsonpath):
"""String解析器"""
return value

@staticmethod
def get_data(value):
return value


class JSONGConfigParser(GConfigParser):
@staticmethod
Expand Down

0 comments on commit b00fc04

Please sign in to comment.