Skip to content

Commit

Permalink
更新全局变量替换逻辑 (#165)
Browse files Browse the repository at this point in the history
* 更新全局变量替换逻辑

* 修复变量赋值

* 删除SQL变量

* 修复类型检查

* 修复环境变量读取

* 捕获请求数据变量解析错误信息
  • Loading branch information
wu-clan authored Mar 13, 2024
1 parent 0e6b019 commit 1862646
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 77 deletions.
22 changes: 16 additions & 6 deletions httpfpt/common/send_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,17 @@ def send_request(
raise SendRequestError('请求发起失败,请使用合法的请求引擎')

# 获取解析后的请求数据
log.info('开始解析请求数据...' if not relate_log else '开始解析关联请求数据...')
log.info('开始解析用例数据...' if not relate_log else '开始解析关联用例数据...')
try:
request_data_parse = RequestDataParse(request_data, request_engin)
parsed_data = request_data_parse.get_request_data_parsed(relate_log)
except Skipped as e:
raise e
except Exception as e:
if not relate_log:
log.error(f'请求数据解析失败: {e}')
log.error(f'用例数据解析失败: {e}')
raise e
log.info('请求数据解析完成' if not relate_log else '关联请求数据解析完成')
log.info('用例数据解析完成' if not relate_log else '关联用例数据解析完成')

# 记录请求前置数据; 此处数据中如果包含关联用例变量, 不会被替换为结果记录, 因为替换动作还未发生
setup = parsed_data['setup']
Expand All @@ -169,7 +169,8 @@ def send_request(
if relate_parsed_data:
parsed_data = relate_parsed_data
elif key == SetupType.SQL:
mysql_client.exec_case_sql(value, parsed_data['env'])
sql = var_extractor.vars_replace({'sql': value}, parsed_data['env'])['sql']
mysql_client.exec_case_sql(sql, parsed_data['env'])
elif key == SetupType.HOOK:
hook_executor.exec_hook_func(value)
elif key == SetupType.WAIT_TIME:
Expand Down Expand Up @@ -212,6 +213,11 @@ def send_request(
request_data_parsed.update({'content': request_data_parsed.pop('body')})
else:
request_data_parsed.update({'data': request_data_parsed.pop('body')})
try:
request_data_parsed = var_extractor.vars_replace(request_data_parsed, parsed_data['env'])
except Exception as e:
log.error(e)
raise e

# 发送请求
response_data = self.init_response_metadata
Expand Down Expand Up @@ -255,13 +261,17 @@ def send_request(
for key, value in item.items():
if value is not None:
if key == TeardownType.SQL:
mysql_client.exec_case_sql(value, parsed_data['env'])
sql = var_extractor.vars_replace({'sql': value}, parsed_data['env'])['sql']
mysql_client.exec_case_sql(sql, parsed_data['env'])
if key == TeardownType.HOOK:
hook_executor.exec_hook_func(value)
if key == TeardownType.EXTRACT:
var_extractor.teardown_var_extract(response_data, value, parsed_data['env'])
if key == TeardownType.ASSERT:
asserter.exec_asserter(response_data, assert_text=value)
assert_text = var_extractor.vars_replace(
target={'assert_text': value}, env=parsed_data['env']
)['assert_text']
asserter.exec_asserter(response_data, assert_text)
elif key == TeardownType.WAIT_TIME:
log.info(f'执行请求后等待:{value} s')
time.sleep(value)
Expand Down
33 changes: 5 additions & 28 deletions httpfpt/db/mysql_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,14 @@
import pymysql

from dbutils.pooled_db import PooledDB
from jsonpath import findall

from httpfpt.common.env_handler import write_env_vars
from httpfpt.common.errors import JsonPathFindError, SQLSyntaxError, VariableError
from httpfpt.common.errors import SQLSyntaxError
from httpfpt.common.log import log
from httpfpt.common.variable_cache import variable_cache
from httpfpt.common.yaml_handler import write_yaml_vars
from httpfpt.core.get_conf import config
from httpfpt.core.path_conf import RUN_ENV_PATH
from httpfpt.enums.query_fetch_type import QueryFetchType
from httpfpt.enums.sql_type import SqlType
from httpfpt.enums.var_type import VarType
from httpfpt.utils.enum_control import get_enum_values
from httpfpt.utils.request.vars_extractor import var_extractor
from httpfpt.utils.request.vars_recorder import record_variables


class MysqlDB:
Expand Down Expand Up @@ -134,18 +128,16 @@ def execute(self, sql: str) -> int:
finally:
self.close(conn, cursor)

def exec_case_sql(self, sql: str, env_filename: str | None = None) -> dict | list | int | None:
def exec_case_sql(self, sql: str, env: str | None = None) -> dict | list | int | None:
"""
执行用例 sql
:param sql:
:param env_filename:
:param env:
:return:
"""
# 获取返回数据
if isinstance(sql, str):
if env_filename is not None:
sql = var_extractor.vars_replace({'sql': sql}, env_filename)['sql']
log.info(f'执行 SQL: {sql}')
if sql.startswith(SqlType.select):
return self.query(sql)
Expand All @@ -159,25 +151,10 @@ def exec_case_sql(self, sql: str, env_filename: str | None = None) -> dict | lis
set_type = sql['type']
sql_text = sql['sql']
json_path = sql['jsonpath']
if env_filename is not None:
sql_text = var_extractor.vars_replace({'sql': sql_text}, env_filename)['sql']
query_data = self.query(sql_text)
if not query_data:
raise SQLSyntaxError('变量提取失败,SQL 查询结果为空')
value = findall(json_path, query_data)
if not value:
raise JsonPathFindError(f'jsonpath 取值失败, 表达式: {json_path}')
value_str = str(value[0])
if set_type == VarType.CACHE:
variable_cache.set(key, value_str)
elif set_type == VarType.ENV:
write_env_vars(RUN_ENV_PATH, env_filename, key, value_str) # type: ignore
elif set_type == VarType.GLOBAL:
write_yaml_vars({key: value_str})
else:
raise VariableError(
f'前置 SQL 设置变量失败, 用例参数 "type: {set_type}" 值错误, 请使用 cache / env / global'
)
record_variables(json_path, query_data, key, set_type, env) # type: ignore

@staticmethod
def sql_verify(sql: str) -> None:
Expand Down
5 changes: 4 additions & 1 deletion httpfpt/utils/request/request_data_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ def _error_msg(info: str) -> str:

class RequestDataParse:
def __init__(self, request_data: dict, request_engin: str):
self.request_data = var_extractor.vars_replace(hook_executor.hook_func_value_replace(request_data))
self.request_data = var_extractor.vars_replace(
hook_executor.hook_func_value_replace(request_data),
exception=False,
)
self.request_engin = request_engin
self._is_run() # put bottom

Expand Down
64 changes: 22 additions & 42 deletions httpfpt/utils/request/vars_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
import os.path
import re

from jsonpath import findall

from httpfpt.common.env_handler import get_env_dict, write_env_vars
from httpfpt.common.errors import JsonPathFindError, RequestDataParseError, VariableError
from httpfpt.common.env_handler import get_env_dict
from httpfpt.common.errors import RequestDataParseError, VariableError
from httpfpt.common.log import log
from httpfpt.common.variable_cache import variable_cache
from httpfpt.common.yaml_handler import read_yaml, write_yaml_vars
from httpfpt.common.yaml_handler import read_yaml
from httpfpt.core.path_conf import RUN_ENV_PATH, TEST_DATA_PATH
from httpfpt.enums.var_type import VarType
from httpfpt.utils.request.vars_recorder import record_variables


class VarsExtractor:
Expand All @@ -26,61 +24,56 @@ def __init__(self) -> None:
self.vars_re = re.compile(r'\${([a-zA-Z_]\w*)}|(?<!\S)\$([a-zA-Z_]\w*)(?!\S)')
# 关联变量表达: ^{var} 或 ^var
self.relate_vars_re = re.compile(r'\^{([a-zA-Z_]\w*)}|(?<!\S)\^([a-zA-Z_]\w*)(?!\S)')
# SQL 变量语法: :{var} 或 :var
self.sql_vars_re = re.compile(r':{([a-zA-Z_]\w*)}|(?<!\S):([a-zA-Z_]\w*)(?!\S)')

def vars_replace(self, target: dict, env_filename: str | None = None) -> dict:
def vars_replace(self, target: dict, env: str | None = None, exception: bool = True) -> dict:
"""
变量替换
:param target:
:param env_filename:
:param env:
:param exception:
:return:
"""
str_target = json.dumps(target, ensure_ascii=False)

match = self.vars_re.search(str_target) or self.sql_vars_re.search(str_target)
match = self.vars_re.search(str_target)
if not match:
return target

# 获取环境名称
env = env_filename or target.get('config', {}).get('request', {}).get('env')
if not env or not isinstance(env, str):
_env = env or target.get('config', {}).get('request', {}).get('env')
if not _env or not isinstance(_env, str):
raise RequestDataParseError('运行环境获取失败, 测试用例数据缺少 config:request:env 参数')
try:
env_file = os.path.join(RUN_ENV_PATH, env)
env_file = os.path.join(RUN_ENV_PATH, _env)
env_vars = get_env_dict(env_file)
except OSError:
raise RequestDataParseError('运行环境获取失败, 请检查测试用例环境配置')

# 获取全局变量
global_vars = read_yaml(TEST_DATA_PATH, filename='global_vars.yaml')

# 获取 re 规则字符串
var_re = self.sql_vars_re if env_filename else self.vars_re
# 执行变量替换
var_re = self.vars_re
for match in var_re.finditer(str_target):
var_key = match.group(1) or match.group(2)
if var_key is not None:
log_type = '请求数据'
try:
# 设置默认值为特殊字符, 避免变量值为 None 时错误判断
default = '`AE86`'
# 替换: 临时变量 > 环境变量 > 全局变量
cache_value = variable_cache.get(var_key, default=default)
if cache_value == default:
global_vars = read_yaml(TEST_DATA_PATH, filename='global_vars.yaml')
var_value = env_vars.get(var_key.upper(), global_vars.get(var_key, default))
if var_value != default:
if env_filename is not None:
log_type = 'SQL '
str_target = var_re.sub(str(var_value), str_target, 1)
log.info(f'{log_type}变量 {var_key} 替换完成')
log.info(f'用例数据变量 {var_key} 替换完成')
else:
raise VariableError(var_key)
else:
str_target = var_re.sub(str(cache_value), str_target, 1)
log.info(f'{log_type}变量 {var_key} 替换完成')
log.info(f'用例数据变量 {var_key} 替换完成')
except Exception as e:
raise VariableError(f'{log_type}变量 {var_key} 替换失败: {e}')
if exception:
raise VariableError(f'用例数据变量 {var_key} 替换失败: {e}')

dict_target = json.loads(str_target)

Expand Down Expand Up @@ -110,11 +103,11 @@ def relate_vars_replace(self, target: dict) -> dict:
if cache_value != default:
try:
str_target = self.relate_vars_re.sub(str(cache_value), str_target, 1)
log.info(f'请求数据关联变量 {var_key} 替换完成')
log.info(f'用例数据关联变量 {var_key} 替换完成')
except Exception as e:
raise VariableError(f'请求数据关联变量 {var_key} 替换失败: {e}')
raise VariableError(f'用例数据关联变量 {var_key} 替换失败: {e}')
else:
raise VariableError(f'请求数据关联变量替换失败,临时变量池不存在变量: "{var_key}"')
raise VariableError(f'用例数据关联变量替换失败,临时变量池不存在变量: "{var_key}"')

log.info('关联测试用例变量替换完毕')
# TODO: https://github.com/StKali/cache3/issues/18
Expand Down Expand Up @@ -142,20 +135,7 @@ def teardown_var_extract(response: dict, extract: dict, env: str) -> None:
key = extract['key']
set_type = extract['type']
json_path = extract['jsonpath']
value = findall(json_path, response)
if not value:
raise JsonPathFindError(f'jsonpath 取值失败, 表达式: {json_path}')
value_str = str(value[0])
if set_type == VarType.CACHE:
variable_cache.set(key, value_str)
elif set_type == VarType.ENV:
write_env_vars(RUN_ENV_PATH, env, key, value_str)
elif set_type == VarType.GLOBAL:
write_yaml_vars({key: value_str})
else:
raise VariableError(
f'前置 SQL 设置变量失败, 用例参数 "type: {set_type}" 值错误, 请使用 cache / env / global'
)
record_variables(json_path, response, key, set_type, env)


var_extractor = VarsExtractor()
35 changes: 35 additions & 0 deletions httpfpt/utils/request/vars_recorder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from jsonpath import findall

from httpfpt.common.env_handler import write_env_vars
from httpfpt.common.errors import JsonPathFindError, VariableError
from httpfpt.common.variable_cache import variable_cache
from httpfpt.common.yaml_handler import write_yaml_vars
from httpfpt.core.path_conf import RUN_ENV_PATH
from httpfpt.enums.var_type import VarType


def record_variables(jsonpath: str, target: dict, key: str, set_type: str, env: str) -> None:
"""
记录变量
:param jsonpath:
:param set_type:
:param target:
:param key:
:param env:
:return:
"""
value = findall(jsonpath, target)
if not value:
raise JsonPathFindError(f'jsonpath 取值失败, 表达式: {jsonpath}')
value_str = str(value[0])
if set_type == VarType.CACHE:
variable_cache.set(key, value_str)
elif set_type == VarType.ENV:
write_env_vars(RUN_ENV_PATH, env, key, value_str)
elif set_type == VarType.GLOBAL:
write_yaml_vars({key: value_str})
else:
raise VariableError(f'变量设置失败, 用例参数 "type: {set_type}" 值错误, 请使用 cache / env / global')

0 comments on commit 1862646

Please sign in to comment.