addproject

This commit is contained in:
qiaoxinjiu
2026-01-22 19:10:37 +08:00
commit 6994b185a3
184 changed files with 21039 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
# -*- coding:utf-8 -*-
# 检查jenkins上的用例构建结果并根据用例失败数量将结果设置为通过或失败
import sys
import os
HERE = os.path.dirname(os.path.abspath(__file__))
WORKSPACE = os.path.abspath(os.path.join(HERE, '../../../'))
sys.path.append(WORKSPACE)
from base_framework.public_tools.xml_file_api import XmlFileApi
PROJECT_NAME = sys.argv[0]
# 指定 Robot Framework 输出文件的路径
HERE = os.path.dirname(os.path.abspath(__file__))
output_file = os.path.abspath(os.path.join(HERE, '../../../Report/out/output.xml'))
# 解析输出文件, 获取失败用例数量
xf = XmlFileApi(file_path=output_file)
result = xf.get_contain_by_tag_names(tag_names='statistics/total/stat')
failed_count = int(result[0]['attrib']['fail'])
# 按失败用例对应的qa人员标签,
if failed_count > 0:
result = xf.get_contain_by_tag_names(tag_names='statistics/tag/stat')
qa_info = {}
qa_failed_case_number = 0
for item in result:
if 'qa-' in item['text'].lower():
fail_case = int(item['attrib']['fail'])
if fail_case > 0:
qa_info[item['text'][3:]] = fail_case
qa_failed_case_number += fail_case
if len(qa_info) > 0:
print("失败用例对应的QA人员及失败用例数量:")
for key, value in qa_info.items():
print(f"{key}: {value}")
if failed_count > qa_failed_case_number:
print(f"无qa标签{failed_count - qa_failed_case_number}")
# 根据失败用例数量设置构建结果
if "-IT" in PROJECT_NAME: # it用例失败用例大于5个就标红
if failed_count > 5:
print(f"测试用例执行失败,共有 {failed_count} 个失败的用例。")
sys.exit(1) # 退出码为1表示构建失败构建结果将展示为红色
elif failed_count > 0:
print("测试用例小范围失败,但是不影响整体结果。")
sys.exit(0) # 退出码为0表示构建成功但此时受jenkins构建后步骤限制将展示为黄色
else:
print("测试用例执行成功,所有用例都通过了。")
sys.exit(0) # 退出码为0表示构建成功构建结果将展示为绿色
else: # st和smoking有失败用例就标红
if failed_count > 0:
print(f"测试用例执行失败,共有 {failed_count} 个失败的用例。")
sys.exit(1) # 退出码为1表示构建失败构建结果将展示为红色
else:
print("测试用例执行成功,所有用例都通过了。")
sys.exit(0) # 退出码为0表示构建成功构建结果将展示为绿色

View File

@@ -0,0 +1,68 @@
# coding: utf-8
from base_framework.public_tools.selenium_api import SeleniumWebUI
import time
class JenkinsAPI(SeleniumWebUI):
def __init__(self):
SeleniumWebUI.__init__(self)
self.project_info = {}
def jenkins_login(self, u_name=None, u_pwd=None):
""" 登陆jenkins """
if not u_name:
u_name = "admin"
u_pwd = "admin"
login_url = "http://39.170.26.156:8256/jenkins/login?from=%2Fjenkins%2F"
self.web_open_url(url=login_url)
self.web_send_keys(locator="//*[@id=\"j_username\"]", text=u_name)
self.web_send_keys(locator="//*[@name=\"j_password\"]", text=u_pwd)
self.web_click_element(locator="//*[@name=\"Submit\"]")
time.sleep(1) # 切换到构建墙
self.web_click_element(locator="//*[text()='1-用户产品组']")
time.sleep(1) # 显示用例数量
self.web_click_element(locator="//*[@title=\"Configure Build Monitor Settings\"]")
time.sleep(1)
self.web_click_checkbox(locator="//*[@id=\"settings-show-test-result\"]")
self.web_click_checkbox(locator="//button[text()=\"Done\"]")
def jenkins_find_all_case_number(self):
""" 从构建墙上读取用例数量 """
elements = self.web_find_elements(locator="//li[@ng-repeat=\"project in jobs track by project.hashCode\"]")
for element in elements:
item = element.text.split('\n')
case_number = item[1].split('/')
self.project_info[item[0]] = case_number[1]
def jenkins_edit_project_config(self, **kwargs):
""" 编辑工程配置信息 """
for jp in self.project_info:
case_number = int(self.project_info[jp])
if jp == "TO-IT":
jp = "TO-IT-TEST"
elif jp == "TO-SMOKING":
jp = "TO-SMOKING_NEW"
project_url = "http://10.250.200.1:8080/jenkins/view/1-%E7%94%A8%E6%88%B7%E4%BA%A7%E5%93%81%E7%BB%84/" \
"job/{}/configure".format(jp)
if "SMOKING" in jp or "-ST" in jp:
threshold = 99.999999 # 冒烟和ST用例需全部构建成功
else:
threshold = round((1-5.5/case_number)*100, 4) # 阈值
self.web_open_url(url=project_url) # 进入设置页面
self.web_send_keys(locator="//input[@name=\"_.unstableThreshold\"]", text=str(threshold))
self.web_click_checkbox(locator="//button[text()=\"保存\"]")
print("{}设置阈值{}完毕....".format(jp, threshold))
time.sleep(1)
if __name__ == '__main__':
ja = JenkinsAPI()
ja.jenkins_login()
time.sleep(3)
ja.jenkins_find_all_case_number()
ja.jenkins_edit_project_config()
time.sleep(3)
ja.web_close_browser()

View File

@@ -0,0 +1 @@
special_team = ["EN", "PZ", "ASTOP", "ASOPE", "ASTWB", "ASORG", "UBRD", "UBJ", "ES", "ASTEA", 'GUE', "TMO", "PB"]

View File

@@ -0,0 +1,684 @@
# -*- coding:utf-8 -*-
import copy
import importlib
import importlib.util as i_util
import inspect
import json
import os
import re
import sys
import traceback
from base_framework.platform_tools.Keywords_service import special_team
token_false_server = ["scm-server", "scm-biz-server", "peppa-qi-api", "lggzt", "cti-manage", "ccwx-api", "peppa-la-core-server"]
add_api_list = ["peppa-teach-api"]
host_servers = ["HULK-ORG-API", "HULK-TEACHER-API", "PEPPA-LA-CORE-SERVER"]
host_servers_except_api = ["HULK-CONTENT-AUDIT-SERVER"]
header_servers = ["hulk-operation-api-server", "hulk-teach-supply-cli-api", "hulk-teach-backend-api"]
uid_server = ['SPARKEDU-API', 'SPARKEDU-SITE-API', 'HUOHUA-SERVICE-API']
eid_server = ['SPARKEDU-SITE-MANAGE', 'SPARKEDU-SITE-SCHEDULER']
def check_server_host(servers, host):
"""
检查不需要token的server是否存在url中
"""
for server in servers:
if server in host:
return True
else:
return False
def get_project_root_path(path="base_framework"):
"""
获取项目目录
"""
o_path = os.getcwd()
try:
project_path = re.search(r"(.*%s)" % path, o_path).group(1)
return os.path.abspath(os.path.join(project_path, os.path.pardir))
except:
pass
Project_Path = get_project_root_path()
from base_framework.public_tools.log import get_logger
from base_framework.public_tools.sqlhelper import MySqLHelper
db = MySqLHelper()
log = get_logger()
def get_classes(module):
classes = []
cls_members = inspect.getmembers(module, inspect.isclass)
for (name, _) in cls_members:
classes.append(name)
return classes
def get_module_by_file_path(file_path):
spec = importlib.util.spec_from_file_location("module_name", file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class SetAttr(object):
def __init__(self, paras):
for key, values in paras.items():
self._add_attr(key, values)
def _add_attr(self, key, value):
self.__setattr__(key, value)
class SwaggerDBInfo(object):
def __init__(self):
pass
@staticmethod
def get_team_swagger_info_by_team(teamname):
sql = "SELECT * FROM sparkatp.swagger_info where team = '%s'" % teamname
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_team_swagger_info_by_id(swagger_id):
sql = "SELECT * FROM sparkatp.swagger_info where id = '%s'" % swagger_id
return db.select_one(sql, choose_db="huohua")
@staticmethod
def get_interface_info_by_url_and_type(request_url, request_type, interface_id):
sql = '''select * from sparkatp.interface_info where in_url = "%s" and type = "%s" and
id = %s''' % (request_url, request_type, interface_id)
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_interface_parameters_by_interface_id(interface_id):
sql = """select * from sparkatp.request_parameters a right join sparkatp.parameters_relation b on
a.id = b.parameter_id where a.interface_id = {} and a.is_need != 2 and b.type = 'request'""".format(
interface_id)
return db.select_all(sql=sql, choose_db="huohua")
@staticmethod
def get_interface_request_demo_info_by_interface_id(interface_id):
sql = 'SELECT * FROM sparkatp.parameters_demo where in_id = "%s" order by id DESC' % interface_id
return db.select_all(sql, choose_db="huohua")
class KWFileOperation(object):
"""
操作xx_interface.py文件
"""
def __init__(self, team):
self.kw_file_path = os.path.abspath(os.path.join(Project_Path,
"{team}".format(team=team), "library",
"{team}_interface.py".format(team=team.upper())))
self.all_keywords = []
self.all_url = {}
self.kw_name_with_url = {}
self.runner = self._get_obj_runner()
self.team = team
self.kw_class = self._get_interface_class_name(self.kw_file_path)
def _clean_demo_to_less_key(self, demo):
demo_result = copy.deepcopy(demo)
for key, value in demo.items():
if not value:
demo_result.pop(key)
lenth = len(demo_result)
paras = "kwargs"
if lenth == 1 and isinstance(list(demo_result.values())[0], list):
paras = "args"
return lenth, demo_result, paras
def _get_interface_class_name(self, file_path):
module = get_module_by_file_path(file_path)
classes = get_classes(module)
for class_ in classes:
if "interface" in class_.lower() and class_.lower().startswith(self.team.lower()):
return class_
def _get_obj_runner(self):
spec = i_util.spec_from_file_location("module_name", self.kw_file_path)
module = i_util.module_from_spec(spec)
spec.loader.exec_module(module)
runner = inspect.getmembers(module)
for module in runner:
if module[0] == "obj_runner":
return module[1]
return runner
def _get_file_content(self):
with open(self.kw_file_path, "r", encoding="UTF-8") as f:
for line in f:
yield line.strip("\n")
def re_write_all_keyword_to_py_file(self):
with open(self.kw_file_path, "r", encoding="UTF-8") as file:
all_content = file.readlines()
index = len(all_content)
with open(self.kw_file_path, "r", encoding="UTF-8") as file1:
for line in file1:
if "if __name__" in line and "__main__" in line:
index = all_content.index(line)
break
for i in range(index - 1, 0, -1):
s = all_content[i]
if all_content[i] != "\n":
index = i
all_content.insert(i + 1, "\n")
break
a = all_content[i + 1]
with open(self.kw_file_path, "w", encoding="UTF-8") as file:
file.writelines(all_content[:index + 1])
def get_all_exist_keywords(self):
for item in self._get_file_content():
kw_match = re.search(r"def\s+(kw_in{1}_\w+)\(", item)
if kw_match:
self.all_keywords.append(kw_match.group(1))
return self.all_keywords
def get_all_exist_keywords_info(self):
for item in self._get_file_content():
kw_match = re.search(r"(kw_in{1}_\w+)\(", item)
if kw_match:
self.all_keywords.append(kw_match.group(1))
return self.all_keywords
def write_content_to_interface_file(self, content, file_path=None):
if not file_path:
file_path = self.kw_file_path
with open(file_path, "a+", encoding="UTF-8") as f:
f.write(content)
def generate_all_keyword_info(self):
"""
获取interface文件内已有关键字的url请求地址和请求方法
"""
temp_dict = {}
all_content = self._get_file_content()
server = ""
for line in all_content:
line_new = line
if "def kw_in_" in line:
kw_name = re.search("(kw_in_[\w+_]+)\(", line).group(1)
if "tools.get_container_ip_from_eureka" in line or "tool.get_container_ip_from_eureka" in line:
server = list(re.search("\([\"\']?([\w\-_]+)[\"\']?|\=[\"\']?([\w\-_]+)[\"\']?", line).groups())
server.remove(None)
server = server[0]
if "self." in line and "=" in line:
temp_dict[line.split("=")[0].strip()] = line.split("=")[1].strip().strip("\"")
if " url =" in line or "url=" in line:
log.info(line)
try:
# urls = list(re.search(r"%s(/[\w+-_\}\{]+/?)\??|\}(/[\w+-_\}\{]+/?)\??", line).groups())
match = re.search(r"%s(/[\w+-_\}\{]+/?)\??|\}(/[\w+-_\}\{]+/?)\??", line)
urls = list(match.groups()) if match else []
except Exception as err:
traceback.print_exc()
continue
# urls.remove(None)
urls = [url for url in urls if url is not None]
if not urls:
continue
url = urls[0]
if "\\" in line:
line_new = all_content.__next__()
try:
host = re.search("obj_runner\.([\w\-_]+)", line_new).group(1).strip().strip(")").strip(",")
except:
if "self." in line:
if self.team.upper() in ["EN", "ASTWB", "ASOPE"]:
host_header = "https://"
else:
host_header = "http://"
server = re.search("self\.([\w\-_]+)", line_new).group(1).strip().strip(")").strip(",")
server = server.replace("_", "-").lower()
if self.team.upper() == 'TMO':
host = host_header + "%s" % 'swagger' + ".qa.huohua.cn"
elif server.upper() in host_servers:
if server.upper() == "HULK-ORG-API":
host = host_header + "%s" % server + ".qa.huohua.cn"
else:
host = host_header + "%s" % "api" + ".qa.allschool.com"
else:
if server.upper() in host_servers_except_api:
host = host_header + "%s" % server + ".qa.allschool.com"
else:
host = host_header + "%s" % server + ".qa.huohua.cn"
if self.team.upper() == "TO":
host = "teach_opt_host"
if re.search("\{\}/\{\}", line) and "self." in line:
para = re.search("(self\.[\w_]+)", line).group(1).strip().strip(")").strip(",")
url = temp_dict[para] + urls[0]
if "?" in url:
url = url.split("?")[0]
# if url.endswith("/"):
# url = url[:len(url) - 1]
if server:
if self.team.upper() in special_team and self.team.upper() != 'TMO':
if url.startswith("/"):
url = host + "%s" % url
else:
url = host + "/%s" % url
elif self.team.upper() == 'TMO':
if url.startswith("/"):
url = '%s/%s%s' % (host, server, url)
else:
url = '%s/%s/%s' % (host, server, url)
else:
try:
if url.startswith("/"):
url = eval("self.runner.%s" % host) + "/%s" % server + url
else:
url = eval("self.runner.%s" % host) + "/%s/" % server + url
except Exception as err:
url = "empty_url"
else:
try:
if url.startswith("/"):
url1 = eval("self.runner.%s" % host)
url = url1 + url
else:
url = eval("self.runner.%s" % host) + url
except Exception as err:
url = "empty_url"
if "req_type=" in line:
method = re.search('req_type="(\w+)"|req_type=\'(\w+)\'', line.replace("\'", "\"")).group(1)
self.all_url[url] = [method, kw_name]
server = ""
return self.all_url
class KWOperation(object):
"""
根据数据库内容生成对应的keywords
"""
def __init__(self, team, kw_instance, server=None):
self.team = team
self.doc_string_table_content_temp = " |{}|{}|{}|{}|{}|"
self.function_name = ""
self.help_doc = None
self.function_content = None
self.has_kw_list = []
self.function_list = []
self.file = kw_instance
self.all_keywords = self.file.get_all_exist_keywords()
self.all_url = self.file.generate_all_keyword_info()
self.kw_name = None
self.kw_string = None
self.kw_array = None
self.server = server
def get_interface_demo(self, interface_id):
return SwaggerDBInfo.get_interface_request_demo_info_by_interface_id(interface_id)
def get_interface_parameters(self, interface_id):
return SwaggerDBInfo.get_interface_parameters_by_interface_id(interface_id)
def get_swagger_info_by_id(self, swagger_id):
return SwaggerDBInfo.get_team_swagger_info_by_id(swagger_id)
def _check_kw_exist(self, keywordname):
if keywordname in self.all_keywords:
return False
else:
return True
def generate_kw_name(self, interface_info, demo):
"""
生成对应关键字方法名称如kw_in_to_get_leads_get(**kwargs)
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
_, name_list, _, _ = self._generate_all_url_paramaters(interface_info.in_url)
if "" in name_list:
name_list.remove("")
name_list.append(interface_info.name)
for item in add_api_list:
if item in interface_info.in_url:
name_list[-1] = 'api_' + name_list[-1]
count = -1
self.kw_name = "kw_in_{}_{}_{}".format(self.team.lower(), name_list[-1], interface_info.type.lower())
while abs(count) <= len(name_list):
if self._check_kw_exist(self.kw_name):
if lenth > 1:
self.function_name = " def {kw_name}(self, **kwargs):".format(kw_name=self.kw_name)
else:
if self.team.upper() in ["TMO"]:
self.function_name = " def {kw_name}(self, **kwargs):".format(kw_name=self.kw_name)
else:
if demo_result:
if isinstance(list(demo_result.values())[0], list):
self.function_name = " def {kw_name}(self, *args, **kwargs):".format(
kw_name=self.kw_name)
else:
self.function_name = " def {kw_name}(self, **kwargs):".format(
kw_name=self.kw_name)
else:
self.function_name = " def {kw_name}(self, **kwargs):".format(
kw_name=self.kw_name)
self.has_kw_list.append(interface_info.id)
self.all_keywords.append(self.kw_name)
return True
else:
count -= 1
self.kw_name += "_" + name_list[count]
else:
self.has_kw_list.append(interface_info.id)
return False
def generate_kw_doc(self, interface_info, parameters_list):
"""
根据接口参数信息生成对应的帮助文档
"""
def set_lenth(para):
return " " + str(para).strip() + " "
def get_normal_value(normal_values):
return normal_values.split(",")[0] if normal_values else 0
doc = []
doc_string_head = " \"\"\""
doc_string_table_head = " | {} | {} | {} | {} | {} |".format("请求参数名".ljust(30), "说明".ljust(15),
"类型".ljust(12), "是否必填".ljust(8),
"如无要求时的值".ljust(8))
doc.append(doc_string_head)
doc.append(" | 功能说明:| {} |".format(interface_info.interface_describe))
doc.append(doc_string_table_head)
# if self.team.upper() not in ["TMO"]:
# doc.append(self.doc_string_table_content_temp.format(set_lenth("is_check"),
# set_lenth("is_check默认空不校验返回有值就校验返回"),
# set_lenth("string"),
# set_lenth("业务case的时候需要传入值"),
# set_lenth("False")))
for parameter in parameters_list:
attr = SetAttr(parameter)
if int(attr.in_body) != 0 and int(attr.is_need) != 2:
doc.append(self.doc_string_table_content_temp.format(set_lenth(attr.name), set_lenth(attr.note),
set_lenth(attr.type), set_lenth(attr.is_need),
set_lenth(get_normal_value(attr.normal_values))))
doc.append(doc_string_head)
return "\n".join(doc)
def _generate_all_url_paramaters(self, url):
"""
拆解url生成host, 生成keyword名的相关名称和url中带{}的参数
"""
head_list = []
para_list = []
host, url_last = re.search(r"(\S+\.cn|\S+\.com)(\S+)", url).groups()
search = re.findall("[\w+\-_]+|[\{\w+\-_\}]+", url_last)
if self.team.upper() in ["TMO"]:
search.pop(0)
url_lasr_temp = url_last.split("/")
url_lasr_temp.pop(1)
url_last = '/'.join(url_lasr_temp)
for item in search:
if "{" in item:
temp = item.replace("{", "").replace("}", "")
para_list.append(temp)
else:
head_list.append(item)
return host, head_list, para_list, url_last
def _generate_url_host(self, host):
"""
根据host生成对应的url后缀比如teach_opt_host
如果需要将host替换成IP则生成对应的IP
"""
url_host = ""
if 'peppa-cc-manage' in host:
url_host = "cc_host"
elif 'crmv2' in host or 'smm' in host or 'xxljob' in host:
url_host = "crm_host"
elif 'scm' in host:
url_host = "scm_host"
elif "la-gate" in host:
url_host = "insights_host"
elif 'la-ai-api-bg' in host or 'la-api-ai' in host:
url_host = "spark_land_host"
elif 'manage' in host:
url_host = "manage_host"
elif 'teach' in host:
url_host = "teach_host"
elif 'teach-opt-api' in host or 'sparkle-manage' in host:
url_host = "teach_opt_host"
elif 'opengalaxy' in host:
url_host = "opengalaxy_host"
elif 'employee-manage' in host:
url_host = "ehr_host"
elif 'peppa-agent-manage' in host:
url_host = "hhr_host"
elif 'peppa-agent-api' in host:
url_host = "hhr_api_host"
elif "teach-message-api" in host:
url_host = "teacher_message_host"
elif "swagger" in host:
url_host = "swagger_host"
elif "peppa-qi-api" in host:
url_host = "qi_host"
elif "scm-server" in host:
url_host = "scm_server_host"
elif "scm-biz-server" in host:
url_host = "scm_biz_server_host"
elif "cti-manage" in host:
url_host = "cti_host"
elif "peppa-conversion-api" in host:
url_host = "uc_host"
elif "la-api" in host:
url_host = "la_api_host"
elif "hulk_content_audit_server" in host:
url_host = "hulk_content_audit"
elif "ccwx-api" in host:
url_host = "cc_weixin_host"
return url_host
def generate_function_url(self, interface_info, demo):
"""
生成对应的url
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
url_list = []
user_kwargs = " user, kwargs = get_user(kwargs)"
url_list.append(user_kwargs)
check_json = " kwargs = convert_json(kwargs)"
if paras_d == "args":
check_json = " args = convert_json(args)"
url_list.append(check_json)
host, _, parameters, url_last = self._generate_all_url_paramaters(interface_info.in_url)
host = re.findall("[\w+-]+", host)
url_host = self._generate_url_host(host)
server = ""
# if self.team.upper() in ["TMO"]:
# url_list.append(" try:\n kwargs = eval(args[0])\n except:")
# url_list.append(" try:\n if args[0] and isinstance(args[0], dict):")
# url_list.append("kwargs = args[0]".rjust(20 + len("kwargs = args[0]"), " "))
# url_list.append("except:".rjust(12 + len("except:"), " "))
# url_list.append("kwargs = kwargs".rjust(16 + len("kwargs = kwargs"), " "))
# url_last_tmp = url_last.split("/")
# server = url_last_tmp.pop(1)
# url_last = "/".join(url_last_tmp)
# ip = " ip = tools.get_container_ip_from_eureka(\"%s\", need_jira_id=True)" % server
# url_list.append(ip)
# host = " obj_runner." + url_host + " = \"http://\" + " + "ip[\"container_ip\"]" + " + \":8080\""
# url_list.append(host)
if self.team.upper() in special_team:
if self.server.upper() in host_servers or self.team.upper() in ["TMO"]:
host_server = self.server.lower().replace("-", "_")
else:
host_server = host[1].replace("-", "_")
url_host = "self.%s" % host_server
else:
if self.server.upper() in host_servers or self.team.upper() in ["TMO"]:
host_server = self.server.lower().replace("-", "_")
url_host = "self.%s" % host_server
# if self.team.upper() == 'LALIVE' and 'la-api' in host:
# url_last = url_last.replace("smart", "api/smart")
if "PEPPA-QI-API".lower() in host:
ip = " obj_runner.%s = \"http://{}:8080\".format(self.get_container_ip('PEPPA-QI-API'))" % url_host
url_list.append(ip)
if parameters:
if lenth > 1:
temp = 'kwargs.get("u")'
else:
temp = 'kwargs'
url = " url = \"%s" + url_last + "\".format("
for item in parameters:
url += "%s=%s, " % (item, "%s.get(\"%s\", \"\")" % (temp, item))
url += ") % "
else:
url = " url = \"%s" + url_last + "\" % "
if self.team.upper() in special_team or self.server.upper() in host_servers:
url += url_host
else:
url += "obj_runner.%s" % url_host
url_list.append(url)
return "\n".join(url_list), server
def generate_function_body(self, interface_info, demo, parameters_all):
"""
生成关键字的body内容
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
host, _, parameters, url_last = self._generate_all_url_paramaters(interface_info.in_url)
func_body = []
if re.search(r"\*(\w+)", self.function_name):
paras = re.search(r"\*(\w+)", self.function_name).group(1)
if self.team.upper() in ["TMO"]:
paras = "kwargs"
content = ' obj_log.info("your input:{0}".format(%s))' % paras
func_body.append(content)
if self.kw_string:
func_body.append(" kwargs = kwargs.get('%s', '')" % parameters_all[0].get("name"))
if self.kw_array:
func_body.append(" kwargs = eval(kwargs.pop('%s'))" % parameters_all[0].get("name"))
else:
paras = None
if lenth > 1:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", ' % interface_info.type
if "d" in demo_result:
resp = resp + 'json=kwargs.get("d"), '
if "p" in demo_result:
resp = resp + 'params=kwargs.get("p"), '
if "h" in demo_result:
resp = resp + 'headers=kwargs.get("h"), '
resp = resp + 'user=user)'
else:
if demo_result:
if "d" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", json=%s, user=user)' % (
interface_info.type, paras)
elif "h" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", headers=%s, user=user)' % (
interface_info.type, paras)
elif "p" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", params=%s, user=user)' % (
interface_info.type, paras)
elif "u" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", user=user)' % (
interface_info.type)
else:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", user=user)' % (
interface_info.type)
if self.team.upper() in special_team or check_server_host(token_false_server, host) or \
self.team.upper() in ["TMO"]:
resp = resp.replace("user=user", "token=False")
try:
if self.server.upper() in host_servers:
resp = resp.replace("token=False", "token=False, user=user")
if self.server.upper() == "HULK-ORG-API":
resp = resp.replace("user=user", "user=user, as_login_type=2")
if self.server.upper() in host_servers_except_api:
resp = resp.replace("token=False", "")
except:
pass
if str(self.server).lower() in header_servers:
header = " header = {'debug-param': 'huangliye@huohua.cn'}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'huangliye@huohua.cn'})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
if str(self.server).upper() in eid_server:
header = " header = {'debug-param': 'eid:%s' % self.eid}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'eid:%s' % self.eid})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
if str(self.server).upper() in uid_server:
header = " header = {'debug-param': 'uid:%s' % self.uid}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'uid:%s' % self.uid})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
func_body.append(resp)
# if self.team.upper() not in ["TMO"] and paras_d == "kwargs":
# func_body.append(" check_resp(is_check, resp)")
func_body.append(" return resp\n")
return "\n".join(func_body)
def get_has_keyword_id_in_db(self):
"""
获取所有已有
"""
keyword_list = []
for key in self.all_url.keys():
interface_name = key.split("/")[-1]
sql = "select * from sparkatp.interface_info where name = '%s' and type = '%s'" % (
interface_name, self.all_url[key][0])
interface_infos = SwaggerDBInfo.get_db_info_by_sql(sql=sql)
for interface_info in interface_infos:
interface_attr = SetAttr(interface_info)
if key.lower() in interface_attr.in_url.lower():
keyword_list.append(interface_attr.id)
return keyword_list
def run_keyword_generage(result_path, interface, demo_info, parameters, keyword):
if demo_info.request_demo:
demo_info = json.loads(demo_info.request_demo)
else:
demo_info = {}
if keyword.all_url.get(interface.in_url) and keyword.all_url.get(interface.in_url)[
0].lower() == interface.type.lower():
msg = "接口url%s,%s,已存在关键字,继续生成用例!" % (interface.type, interface.in_url)
log.warning(msg)
keyword.file.write_content_to_interface_file(msg + "\n", result_path)
return True
else:
exist = keyword.generate_kw_name(interface, demo_info)
if exist:
keyword.function_list = []
doc = keyword.generate_kw_doc(interface, parameters)
url, temp = keyword.generate_function_url(interface, demo_info)
content = keyword.generate_function_body(interface, demo_info, parameters)
keyword.function_list.append("\n".join(("", keyword.function_name, doc, url, content)))
keyword.file.re_write_all_keyword_to_py_file()
keyword.file.write_content_to_interface_file("\n".join(keyword.function_list))
keyword.file.all_url[interface.in_url] = [interface.type, keyword.kw_name]
return True
else:
msg = "接口url%s,%s,无法生成关键字,请检查!" % (interface.type, interface.in_url)
log.warning(msg)
keyword.file.write_content_to_interface_file(msg + "\n", result_path)
return False

View File

@@ -0,0 +1,666 @@
# -*- coding:utf-8 -*-
import copy
import importlib
import importlib.util as i_util
import inspect
import json
import os
import re
import sys
import traceback
from base_framework.platform_tools.Keywords_service import special_team
token_false_server = ["scm-server", "scm-biz-server", "peppa-qi-api", "lggzt", "cti-manage", "ccwx-api"]
add_api_list = ["peppa-teach-api"]
host_servers = ["HULK-ORG-API", "HULK-TEACHER-API"]
host_servers_except_api = ["HULK-CONTENT-AUDIT-SERVER"]
header_servers = ["hulk-operation-api-server", "hulk-teach-supply-cli-api", "hulk-teach-backend-api"]
uid_server = ['SPARKEDU-API', 'SPARKEDU-SITE-API']
eid_server = ['SPARKEDU-SITE-MANAGE', 'SPARKEDU-SITE-SCHEDULER']
def check_server_host(servers, host):
"""
检查不需要token的server是否存在url中
"""
for server in servers:
if server in host:
return True
else:
return False
def get_project_root_path(path="base_framework"):
"""
获取项目目录
"""
o_path = os.getcwd()
try:
project_path = re.search(r"(.*%s)" % path, o_path).group(1)
return os.path.abspath(os.path.join(project_path, os.path.pardir))
except:
pass
Project_Path = get_project_root_path()
from base_framework.public_tools.log import get_logger
from base_framework.public_tools.sqlhelper import MySqLHelper
db = MySqLHelper()
log = get_logger()
def get_classes(module):
classes = []
cls_members = inspect.getmembers(module, inspect.isclass)
for (name, _) in cls_members:
classes.append(name)
return classes
def get_module_by_file_path(file_path):
spec = importlib.util.spec_from_file_location("module_name", file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class SetAttr(object):
def __init__(self, paras):
for key, values in paras.items():
self._add_attr(key, values)
def _add_attr(self, key, value):
self.__setattr__(key, value)
class SwaggerDBInfo(object):
def __init__(self):
pass
@staticmethod
def get_team_swagger_info_by_team(teamname):
sql = "SELECT * FROM sparkatp.swagger_info where team = '%s'" % teamname
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_team_swagger_info_by_id(swagger_id):
sql = "SELECT * FROM sparkatp.swagger_info where id = '%s'" % swagger_id
return db.select_one(sql, choose_db="huohua")
@staticmethod
def get_interface_info_by_url_and_type(request_url, request_type, team):
sql = 'select * from sparkatp.interface_info where in_url = "%s" and type = "%s" and swagger_id in (select id from sparkatp.swagger_info where team = "%s")' % (request_url, request_type, team)
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_interface_parameters_by_interface_id(interface_id):
sql = "select * from sparkatp.request_parameters a right join sparkatp.parameters_relation b on a.id = b.parameter_id where a.interface_id = {} and b.type = 'request' and a.offline=0".format(
interface_id)
return db.select_all(sql=sql, choose_db="huohua")
@staticmethod
def get_interface_request_demo_info_by_interface_id(interface_id):
sql = 'SELECT * FROM sparkatp.parameters_demo where in_id = "%s" order by id DESC' % interface_id
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_swagger_url_by_team_and_server(team, server):
sql = "select * from sparkatp.swagger_info where team='%s' and server_name = '%s';" % (team, server)
return db.select_one(sql, choose_db="huohua")
class KWFileOperation(object):
"""
操作xx_interface.py文件
"""
def __init__(self, team, server):
self.kw_file_path = os.path.abspath(os.path.join(Project_Path,
"{team}".format(team=team), "library",
"{team}_interface.py".format(team=team.upper())))
self.team = team
self.server = server
self.all_url = {}
self.all_keywords = []
self.runner = self._get_obj_runner()
self.file_content = self.re_write_all_keyword_to_py_file()
self.check_server_and_rewrite_server()
self.get_all_exist_keywords()
self.generate_all_keyword_info()
self.kw_class = self._get_interface_class_name(self.kw_file_path)
def check_server_and_rewrite_server(self):
"""
检查server是否在interface.py文件中配置获取IP
"""
start_index = 0
empty_index = 0
server_host = self.server.lower().replace('-', '_')
server_upper = self.server.upper()
if "self.%s =" % server_host in ''.join(self.file_content) or '':
return
for line in self.file_content:
if "def __init__(self):" in line:
start_index = self.file_content.index(line) + 1
log.info(start_index)
if start_index == 0:
continue
if " def " in line and "__init__" not in line:
end_index = self.file_content.index(line)
log.info(end_index)
break
if "pass" in self.file_content[start_index]:
self.file_content[
start_index] = ' self.need_jira_id = True if ReadConfig(env_choose_path).get_value("run_jira_id", "huohua-podenv") else False\n'
elif 'self.need_jira_id = True if ' not in self.file_content[start_index] and "pass" in self.file_content[
start_index]:
self.file_content.insert(start_index,
' self.need_jira_id = True if ReadConfig(env_choose_path).get_value("run_jira_id", "huohua-podenv") else False\n')
for item in range(start_index, end_index):
if self.file_content[item] == "\n":
self.file_content.insert(item,
" self.%s_ip = obj_tool.get_container_ip_from_eureka('%s', need_jira_id=self.need_jira_id)\n" % (
server_host, server_upper))
self.file_content.insert(item + 1,
' self.%s = "http://" + self.%s_ip["container_ip"] + ":8080"\n' % (
server_host, server_host))
break
with open(self.kw_file_path, "w", encoding="UTF-8") as file:
for line1 in self.file_content:
file.writelines(line1)
def _clean_demo_to_less_key(self, demo):
demo_result = copy.deepcopy(demo)
for key, value in demo.items():
if not value:
demo_result.pop(key)
lenth = len(demo_result)
paras = "kwargs"
if lenth == 1 and isinstance(list(demo_result.values())[0], list):
paras = "args"
return lenth, demo_result, paras
def _get_interface_class_name(self, file_path):
module = get_module_by_file_path(file_path)
classes = get_classes(module)
for class_ in classes:
if "interface" in class_.lower():
return class_
def _get_obj_runner(self):
spec = i_util.spec_from_file_location("module_name", self.kw_file_path)
module = i_util.module_from_spec(spec)
spec.loader.exec_module(module)
runner = inspect.getmembers(module)
for module in runner:
if module[0] == "obj_runner":
return module[1]
return runner
def _get_file_content(self):
with open(self.kw_file_path, "r", encoding="UTF-8") as file:
all_content = file.readlines()
return all_content
def re_write_all_keyword_to_py_file(self):
with open(self.kw_file_path, "r", encoding="UTF-8") as file:
all_content = file.readlines()
index = len(all_content)
with open(self.kw_file_path, "r", encoding="UTF-8") as file1:
for line in file1:
if "if __name__" in line and "__main__" in line:
index = all_content.index(line)
break
for i in range(index - 1, 0, -1):
s = all_content[i]
if all_content[i] != "\n":
index = i
all_content.insert(i + 1, "\n")
break
a = all_content[i + 1]
with open(self.kw_file_path, "w", encoding="UTF-8") as file:
file.writelines(all_content[:index + 1])
return all_content[:index + 1]
def get_all_exist_keywords(self):
for item in self.file_content:
kw_match = re.search(r"def\s+(kw_in{1}_\w+)\(", item)
if kw_match:
self.all_keywords.append(kw_match.group(1))
return self.all_keywords
def write_content_to_interface_file(self, content, file_path=None):
if not file_path:
file_path = self.kw_file_path
with open(file_path, "a+", encoding="UTF-8") as f:
f.write(content)
def generate_all_keyword_info(self):
"""
获取interface文件内已有关键字的url请求地址和请求方法
"""
temp_dict = {}
all_content = self.file_content
server = False
kw_line = False
url_line = False
host = False
for line in all_content:
if "def kw_in_" in line:
kw_line = True
kw_name = re.search("(kw_in_[\w+_]+)\(", line).group(1)
if kw_line:
try:
if "get_container_ip" in line:
server = list(re.search(r'get_container_ip.*\([\'\"]([\w\-_]+)[\'\"]', line).groups())
server = server[0]
except Exception as err:
pass
if "self." in line and "=" in line:
temp_dict[line.split("=")[0].strip()] = line.split("=")[1].strip().strip("\"")
if (" url =" in line or "url=" in line) and 'url +' not in line:
log.info(line)
try:
urls = list(re.search(r"%s(/[\w+-_\}\{]+/?)\??|\}(/[\w+-_\}\{]+/?)\??", line).groups())
urls.remove(None)
except Exception as err:
log.info("error: %s" % line)
url_line = True
if url_line:
if 'url = "%s/api/customer/{customerId}/give_up"' in line:
print()
if not host:
try:
host_key = re.search("obj_runner\.([\w\-_]+)", line).group(1).strip().strip(")").strip(",")
host = eval("self.runner.%s" % host_key)
except:
if not server:
if "self." in line:
server = re.search("self\.([\w\-_]+)", line).group(1).strip().strip(")").strip(",")
server = server.replace("_", "-").lower()
if server:
try:
host_temp = SwaggerDBInfo.get_swagger_url_by_team_and_server(self.team, server)['sw_url']
host = host_temp.split("/v2")[0]
except Exception as err:
host = ""
if host:
url = host + urls[0]
if "req_type=" in line:
method = re.search('req_type="(\w+)"|req_type=\'(\w+)\'', line.replace("\'", "\"")).group(1)
self.all_url[url] = [method, kw_name]
server = False
kw_line = False
url_line = False
host = False
return self.all_url
class KWOperation(object):
"""
根据数据库内容生成对应的keywords
"""
def __init__(self, team, kw_instance):
self.team = team
self.doc_string_table_content_temp = " |{}|{}|{}|{}|{}|"
self.function_name = ""
self.help_doc = None
self.function_content = None
self.has_kw_list = []
self.function_list = []
self.file = kw_instance
self.all_keywords = self.file.all_keywords
self.all_url = self.file.all_url
self.kw_name = None
self.kw_string = None
self.server = kw_instance.server
def get_interface_demo(self, interface_id):
return SwaggerDBInfo.get_interface_request_demo_info_by_interface_id(interface_id)
def get_interface_parameters(self, interface_id):
return SwaggerDBInfo.get_interface_parameters_by_interface_id(interface_id)
def get_swagger_info_by_id(self, swagger_id):
return SwaggerDBInfo.get_team_swagger_info_by_id(swagger_id)
def _check_kw_exist(self, keywordname):
if keywordname in self.all_keywords:
return False
else:
return True
def generate_kw_name(self, interface_info, demo):
"""
生成对应关键字方法名称如kw_in_to_get_leads_get(is_check='', **kwargs)
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
_, name_list, _, _ = self._generate_all_url_paramaters(interface_info.in_url)
if "" in name_list:
name_list.remove("")
name_list.append(interface_info.name)
for item in add_api_list:
if item in interface_info.in_url:
name_list[-1] = 'api_' + name_list[-1]
count = -1
self.kw_name = "kw_in_{}_{}_{}".format(self.team.lower(), name_list[-1], interface_info.type.lower())
while abs(count) <= len(name_list):
if self._check_kw_exist(self.kw_name):
if lenth > 1:
self.function_name = " def {kw_name}(self, is_check='', **kwargs):".format(kw_name=self.kw_name)
else:
if self.team.upper() in ["TMO"]:
self.function_name = " def {kw_name}(self, *args, **kwargs):".format(kw_name=self.kw_name)
else:
if demo_result:
if isinstance(list(demo_result.values())[0], list):
self.function_name = " def {kw_name}(self, *args, **kwargs):".format(
kw_name=self.kw_name)
else:
self.function_name = " def {kw_name}(self, is_check='', **kwargs):".format(
kw_name=self.kw_name)
else:
self.function_name = " def {kw_name}(self, is_check='', **kwargs):".format(
kw_name=self.kw_name)
self.has_kw_list.append(interface_info.id)
self.all_keywords.append(self.kw_name)
return True
else:
count -= 1
self.kw_name += "_" + name_list[count]
else:
self.has_kw_list.append(interface_info.id)
return False
def generate_kw_doc(self, interface_info, parameters_list):
"""
根据接口参数信息生成对应的帮助文档
"""
def set_lenth(para):
return " " + str(para).strip() + " "
doc = []
doc_string_head = " \"\"\""
doc_string_table_head = " | {}| {}| {}| {}| {}|".format("请求参数名".ljust(30), "说明".ljust(15),
"类型".ljust(12), "条件".ljust(10),
"是否必填".ljust(8))
doc.append(doc_string_head)
doc.append(" {} + {} + interface id: {}".format(interface_info.interface_describe, interface_info.type,
interface_info.id))
doc.append(" url: " + interface_info.in_url)
doc.append(doc_string_table_head)
if self.team.upper() not in ["TMO"]:
doc.append(self.doc_string_table_content_temp.format(set_lenth("is_check"),
set_lenth("is_check默认空不校验返回有值就校验返回"),
set_lenth("string"),
set_lenth("业务case的时候需要传入值"),
set_lenth("False")))
for parameter in parameters_list:
attr = SetAttr(parameter)
if int(attr.in_body) != 0 and int(attr.is_need) != 2:
doc.append(self.doc_string_table_content_temp.format(set_lenth(attr.name), set_lenth(attr.note),
set_lenth(attr.type), set_lenth(attr.p_condition),
set_lenth(attr.is_need)))
doc.append(doc_string_head)
return "\n".join(doc)
def _generate_all_url_paramaters(self, url):
"""
拆解url生成host, 生成keyword名的相关名称和url中带{}的参数
"""
head_list = []
para_list = []
host, url_last = re.search(r"(\S+\.cn|\S+\.com)(\S+)", url).groups()
search = re.findall("[\w+\-_]+|[\{\w+\-_\}]+", url_last)
for item in search:
if "{" in item:
temp = item.replace("{", "").replace("}", "")
para_list.append(temp)
else:
head_list.append(item)
return host, head_list, para_list, url_last
def _generate_url_host(self):
"""
根据host生成对应的url后缀比如teach_opt_host
如果需要将host替换成IP则生成对应的IP
"""
server_temp = self.server.lower().replace('-', '_')
url_host = 'self.%s' % server_temp
# if self.server:
# url_host = ""
# url_host = ""
# if 'peppa-cc-manage' in host:
# url_host = "cc_host"
# elif 'crmv2' in host or 'smm' in host or 'xxljob' in host:
# url_host = "crm_host"
# elif 'scm' in host:
# url_host = "scm_host"
# elif "la-gate" in host:
# url_host = "insights_host"
# elif 'la-ai-api-bg' in host or 'la-api-ai' in host:
# url_host = "spark_land_host"
# elif 'manage' in host:
# url_host = "manage_host"
# elif 'teach' in host:
# url_host = "teach_host"
# elif 'teach-opt-api' in host or 'sparkle-manage' in host:
# url_host = "teach_opt_host"
# elif 'opengalaxy' in host:
# url_host = "opengalaxy_host"
# elif 'employee-manage' in host:
# url_host = "ehr_host"
# elif 'peppa-agent-manage' in host:
# url_host = "hhr_host"
# elif 'peppa-agent-api' in host:
# url_host = "hhr_api_host"
# elif "teach-message-api" in host:
# url_host = "teacher_message_host"
# elif "swagger" in host:
# url_host = "swagger_host"
# elif "peppa-qi-api" in host:
# url_host = "qi_host"
# elif "scm-server" in host:
# url_host = "scm_server_host"
# elif "scm-biz-server" in host:
# url_host = "scm_biz_server_host"
# elif "cti-manage" in host:
# url_host = "cti_host"
# elif "peppa-conversion-api" in host:
# url_host = "uc_host"
# elif "la-api" in host:
# url_host = "la_api_host"
# elif "hulk_content_audit_server" in host:
# url_host = "hulk_content_audit"
return url_host
def generate_function_url(self, interface_info, demo):
"""
生成对应的url
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
url_list = []
user_kwargs = " user, kwargs = get_user(kwargs)"
url_list.append(user_kwargs)
check_json = " kwargs = convert_json(kwargs)"
if paras_d == "args":
check_json = " args = convert_json(args)"
url_list.append(check_json)
host, _, parameters, url_last = self._generate_all_url_paramaters(interface_info.in_url)
host = re.findall("[\w+-]+", host)
url_host = self._generate_url_host()
server = ""
if self.team.upper() in ["TMO"]:
url_list.append(" try:\n kwargs = eval(args[0])\n except:")
url_list.append(" try:\n if args[0] and isinstance(args[0], dict):")
url_list.append("kwargs = args[0]".rjust(20 + len("kwargs = args[0]"), " "))
url_list.append("except:".rjust(12 + len("except:"), " "))
url_list.append("kwargs = kwargs".rjust(16 + len("kwargs = kwargs"), " "))
url_last_tmp = url_last.split("/")
server = url_last_tmp.pop(1)
url_last = "/".join(url_last_tmp)
if parameters:
if lenth > 1:
temp = 'kwargs.get("u")'
else:
temp = 'kwargs'
url = " url = \"%s" + url_last + "\".format("
for item in parameters:
url += "%s=%s, " % (item, "%s.get(\"%s\", \"\")" % (temp, item))
url += ") % "
else:
url = " url = \"%s" + url_last + "\" % "
url += url_host
url_list.append(url)
return "\n".join(url_list), server
def generate_function_body(self, interface_info, demo, parameters_all):
"""
生成关键字的body内容
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
host, _, parameters, url_last = self._generate_all_url_paramaters(interface_info.in_url)
func_body = []
if re.search(r"\*(\w+)", self.function_name):
paras = re.search(r"\*(\w+)", self.function_name).group(1)
if self.team.upper() in ["TMO"]:
paras = "kwargs"
content = ' obj_log.info("your input:{0}".format(%s))' % paras
func_body.append(content)
if self.kw_string:
func_body.append(" kwargs = kwargs.get('%s', '')" % parameters_all[0].get("name"))
else:
paras = None
if lenth > 1:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", ' % interface_info.type
if "d" in demo_result:
resp = resp + 'json=kwargs.get("d"), '
if "p" in demo_result:
resp = resp + 'params=kwargs.get("p"), '
if "h" in demo_result:
resp = resp + 'headers=kwargs.get("h"), '
resp = resp + 'user=user)'
else:
if demo_result:
if "d" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", json=%s, user=user)' % (
interface_info.type, paras)
elif "h" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", headers=%s, user=user)' % (
interface_info.type, paras)
elif "p" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", params=%s, user=user)' % (
interface_info.type, paras)
elif "u" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", user=user)' % (
interface_info.type)
else:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", user=user)' % (
interface_info.type)
if self.team.upper() in special_team or check_server_host(token_false_server, host) or self.team.upper() in [
"TMO"]:
resp = resp.replace("user=user", "token=False")
try:
if self.server.upper() in host_servers:
resp = resp.replace("token=False", "token=False, user=user")
if self.server.upper() == "HULK-ORG-API":
resp = resp.replace("user=user", "user=user, as_login_type=2")
if self.server.upper() in host_servers_except_api:
resp = resp.replace("token=False", "")
except:
pass
if str(self.server).lower() in header_servers:
header = " header = {'debug-param': 'huangliye@huohua.cn'}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'huangliye@huohua.cn'})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
if str(self.server).upper() in eid_server:
header = " header = {'debug-param': 'eid:%s' % self.eid}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'eid:%s' % self.eid})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
if str(self.server).upper() in uid_server:
header = " header = {'debug-param': 'uid:%s' % self.uid}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'uid:%s' % self.uid})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
func_body.append(resp)
if self.team.upper() not in ["TMO"] and paras_d == "kwargs":
func_body.append(" check_resp(is_check, resp)")
func_body.append(" return resp\n")
return "\n".join(func_body)
def get_has_keyword_id_in_db(self):
"""
获取所有已有
"""
keyword_list = []
for key in self.all_url.keys():
interface_name = key.split("/")[-1]
sql = "select * from sparkatp.interface_info where name = '%s' and type = '%s'" % (
interface_name, self.all_url[key][0])
interface_infos = SwaggerDBInfo.get_db_info_by_sql(sql=sql)
for interface_info in interface_infos:
interface_attr = SetAttr(interface_info)
if key.lower() in interface_attr.in_url.lower():
keyword_list.append(interface_attr.id)
return keyword_list
def run_keyword_generage(result_path, interface, demo_info, parameters, keyword):
if demo_info.request_demo:
demo_info = json.loads(demo_info.request_demo)
else:
demo_info = {}
if keyword.all_url.get(interface.in_url) and keyword.all_url.get(interface.in_url)[
0].lower() == interface.type.lower():
msg = "接口url%s,%s,已存在关键字,继续生成用例!" % (interface.type, interface.in_url)
log.warning(msg)
keyword.file.write_content_to_interface_file(msg + "\n", result_path)
return True
else:
exist = keyword.generate_kw_name(interface, demo_info)
if exist:
keyword.function_list = []
doc = keyword.generate_kw_doc(interface, parameters)
url, temp = keyword.generate_function_url(interface, demo_info)
content = keyword.generate_function_body(interface, demo_info, parameters)
keyword.function_list.append("\n".join(("", keyword.function_name, doc, url, content)))
keyword.file.re_write_all_keyword_to_py_file()
keyword.file.write_content_to_interface_file("\n".join(keyword.function_list))
keyword.file.all_url[interface.in_url] = [interface.type, keyword.kw_name]
return True
else:
msg = "接口url%s,%s,无法生成关键字,请检查!" % (interface.type, interface.in_url)
log.warning(msg)
keyword.file.write_content_to_interface_file(msg + "\n", result_path)
return False
if __name__ == "__main__":
kw = KWFileOperation("CC", "peppa-qi-api")
kw.check_server_and_rewrite_server()

View File

@@ -0,0 +1,3 @@
目录结构说明:
使用者:王刚
用途存放自动生成py文件关键字的脚本

View File

@@ -0,0 +1,141 @@
# -*- coding:utf-8 -*-
"""
功能:发送飞书消息接口
"""
import requests
import json
import logging
import time
import urllib3
urllib3.disable_warnings()
import os
from base_framework.public_tools.read_config import ReadConfig
try:
JSONDecodeError = json.decoder.JSONDecodeError
except AttributeError:
JSONDecodeError = ValueError
HERE = os.path.dirname(os.path.abspath(__file__))
msg_config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'msg_config.ini')
read_config = ReadConfig(filename=msg_config_path)
def get_feishu_config_options():
"""
功能:获取飞书配置文件信息
option_key:配置文件中的key
"""
return read_config.get_options('feishu_user_list')
def get_feishu_config_value(option_key):
"""
功能:获取飞书配置文件信息
option_key:配置文件中的key
"""
return read_config.get_value(sections='feishu_user_list', options=option_key)
def get_user_name_by_email_prefix(email_prefix):
"""
功能:获取飞书配置文件中的邮箱前缀与中文姓名的对应关系
email_prefix:邮箱前缀
返回:对应的中文姓名
"""
return read_config.get_value(sections='user_name', options=email_prefix)
class FeiShuMessage(object):
def __init__(self, team='TO', secret=None, pc_slide=False, fail_notice=False):
"""
机器人初始化
:param team: 业务组名用于从msg_config配置文件中读取对应群组的webhook地址
:param secret: 机器人安全设置页面勾选“加签”时需要传入的密钥
:param pc_slide: 消息链接打开方式默认False为浏览器打开设置为True时为PC端侧边栏打开
:param fail_notice: 消息发送失败提醒默认为False不提醒开发者可以根据返回的消息发送结果自行判断和处理
"""
super(FeiShuMessage, self).__init__()
self.headers = {'Content-Type': 'application/json; charset=utf-8'}
team_name = team.lower() + '_webhook_token'
if team_name in get_feishu_config_options():
token = get_feishu_config_value(option_key=team_name)
self.webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/{0}".format(token)
else:
self.webhook = None
logging.error("Team: {} not in msg_config.ini".format(team))
return
self.secret = secret
self.pc_slide = pc_slide
self.fail_notice = fail_notice
def send_text(self, msg):
"""
消息类型为text类型
:param msg: 消息内容
:return: 返回消息发送结果
"""
data = {"msg_type": "text"}
if msg and self.webhook: # 传入msg非空
data["content"] = {"text": msg}
if "全部通过" in msg:
# 不用发飞书消息
logging.info("+++++++++ 全部构建成功,不发消息 ++++++++")
return False
return self.post(data)
def post(self, data):
"""
发送消息内容UTF-8编码
:param data: 消息数据(字典)
:return: 返回消息发送结果
"""
try:
post_data = json.dumps(data)
response = requests.post(self.webhook, headers=self.headers, data=post_data, verify=False)
except requests.exceptions.HTTPError as exc:
logging.error("消息发送失败, HTTP error: %d, reason: %s" % (exc.response.status_code, exc.response.reason))
raise
except requests.exceptions.ConnectionError:
logging.error("消息发送失败HTTP connection error!")
raise
except requests.exceptions.Timeout:
logging.error("消息发送失败Timeout error!")
raise
except requests.exceptions.RequestException:
logging.error("消息发送失败, Request Exception!")
raise
else:
try:
result = response.json()
except JSONDecodeError:
logging.error("服务器响应异常,状态码:%s,响应内容:%s" % (response.status_code, response.text))
return {'errcode': 500, 'errmsg': '服务器响应异常'}
else:
logging.debug('发送结果:%s' % result)
# 消息发送失败提醒errcode 不为 0表示消息发送异常默认不提醒开发者可以根据返回的消息发送结果自行判断和处理
if self.fail_notice and result.get('errcode', True):
time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
error_data = {
"msgtype": "text",
"text": {
"content": "[注意-自动通知]飞书机器人消息发送失败,时间:%s,原因:%s,请及时跟进,谢谢!" % (
time_now, result['errmsg'] if result.get('errmsg', False) else '未知异常')
},
"at": {
"isAtAll": False
}
}
logging.error("消息发送失败,自动通知:%s" % error_data)
requests.post(self.webhook, headers=self.headers, data=json.dumps(error_data))
return result
if __name__ == '__main__':
fs = FeiShuMessage(team='TO')
吴勇刚 = "<at user_id='ou_94f57439f58bde9376189a3cabb0b11a'>吴勇刚</at>"
刘明浩 = "<at user_id='ou_94f57439f58bde9376189a3cabb0b11b'>刘明浩</at>"
陈慧宗 = "<at user_id='ou_bc2b266b05a428959bfcff3378af2d36'>陈慧宗</at>"
message = "【TO-SMOKING_NEW】 第【1314】次构建结果失败【5】个 \n|--{}: 1个\n|--{}: 2个\n|--{}: 3个" \
"构建报告:...........test..........".format(吴勇刚, 刘明浩, 陈慧宗)
resp = fs.send_text(msg=message)
print(resp)

View File

@@ -0,0 +1,101 @@
# encoding: utf-8
# @Time : 2021/12/20 18:14
# @Author : yk
# @Site :
# @File : jira_message_by_ding.py
from jira import JIRA
import json
import requests
import datetime
import urllib.parse
class JiraObj:
def __init__(self, user, pwd, jira_addr):
if not hasattr(JiraObj, 'jira'):
JiraObj.jira_obj(user, pwd, jira_addr)
@staticmethod
def jira_obj(user, pwd, jira_addr):
JiraObj.jira = JIRA(auth=(user, pwd), options={'server': jira_addr})
@staticmethod
def search_by_jql(jql, fields=None):
return JiraObj.jira.search_issues(jql, fields=fields, maxResults=-1, json_result='true')
def get_delay_sub_task_creator(jira_obj, now_date):
name_list="chengpu,fengtian,dengzhenbo,guosongchao,handongtang,huanghaifeng,lidaijun,wanglushun,wangyujie02,yangwenlei01,yangwenlei01,zhanghaodong,zhengxin01,zhuzipeng"
# name_list="zhanghaodong"
jql = 'issuetype = 缺陷 and createdDate < \'{} 18:00\' AND status not in (QA测试,SIM验证,Done,关闭,待测试) and assignee in ({})'.format(
now_date,name_list)
fields = 'assignee'
print(jql)
issue_list = jira_obj.search_by_jql(jql, fields).get('issues')
print(issue_list)
if issue_list:
return list(map(lambda x: x.get('fields').get('assignee').get('name'), issue_list))
else:
return None
def send_message_by_feishu(name, message):
data={"list":[name]}
a=requests.post(url="http://sqe.qc.huohua.cn:8082/feishu/getUserIds",data=data)
print(a.text)
open_id=a.text.split("open_id")[1].split('"')[2].split("\\")[0]
data={
"app_id": "cli_a2d926427f39900d",
"app_secret": "CvETCGh3rHu6CtcnxzaWK7rMnVgcSLED"
}
a=requests.post(url="https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",json=data)
tenant_access_token=eval(a.text)["tenant_access_token"]
data ={"msg_type": "text","content": { "text": "{}".format(message) }, "open_ids": [open_id]}
header={"Authorization":"Bearer {}".format(tenant_access_token),"Content-Type":"application/json; charset=utf-8"}
a=requests.post(url="https://open.feishu.cn/open-apis/message/v4/batch_send/", json=data,headers=header)
print(a.text)
if __name__ == '__main__':
now_date = datetime.datetime.now().strftime('%Y-%m-%d')
jira_obj = JiraObj('yaokun', 'Cyjayk1314', 'https://jira.bg.huohua.cn')
creator = get_delay_sub_task_creator(jira_obj, now_date)
if creator:
creator=list(set(creator))
message="你还有未解决的bug请及时解决若已解决请及时更新jira状态"
for i in creator:
jql = 'issuetype = 缺陷 and createdDate < \'{} 18:00\' AND status not in (QA测试,SIM验证,Done,关闭,待测试) and assignee in ({})'.format(
now_date, i)
print(jql)
i=i+"@sparkedu.com"
a=jira_obj.search_by_jql(jql, "assignee").get('issues')
for j in a:
print(j["key"])
send_message_by_feishu(i,"你有未修复的bug请及时修复https://jira.bg.huohua.cn/browse/{}".format(j["key"]))
# send_message_by_feishu("yaokun@sparkedu.com","https://jira.bg.huohua.cn/browse/HHC-50790")
# data={"list":["yaokun@sparkedu.com"]}
# a=requests.post(url="http://sqe.qc.huohua.cn:8082/feishu/getUserIds",data=data)
#
# print(a.text.split("open_id")[1].split('"')[2].split("\\")[0])
# open_id="ou_95feb664191332a7916bb3b0d886f553"
# print(a.text)
# data={
# "app_id": "cli_a2d926427f39900d",
# "app_secret": "CvETCGh3rHu6CtcnxzaWK7rMnVgcSLED"
# }
# a=requests.post(url="https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",json=data)
# print(a.text)
#
# data ={"msg_type": "text","content": { "text": "https://jira.bg.huohua.cn/browse/HHC-50790" }, "open_ids": [open_id]}
#
# header={"Authorization":"Bearer t-719d260f474e6a1a6b4a5d990a6bccec6fc3b17f","Content-Type":"application/json; charset=utf-8"}
# print(data)
# a = requests.post(url="https://open.feishu.cn/open-apis/message/v4/batch_send/", json=data,headers=header)
# print(a.text)

View File

@@ -0,0 +1 @@
# to be add 。。。。

View File

@@ -0,0 +1,236 @@
# 此文档中配置钉钉,飞书群组和群用户信息
[feishu_user_list]
JENKINS_webhook_token = 6de2e886-10de-4a85-aa92-2d7446d0c315
CDQA_webhook_token = 30e400ed-b802-4afb-b38c-2a0690c1a2a6
TO_webhook_token = 2109acc2-6ffe-4038-8bf1-4bfcfcd4cb01
TMO_webhook_token = 2109acc2-6ffe-4038-8bf1-4bfcfcd4cb01
HHI_TO_webhook_token = 2109acc2-6ffe-4038-8bf1-4bfcfcd4cb01
HHI_TMO_webhook_token = 2109acc2-6ffe-4038-8bf1-4bfcfcd4cb01
GUE_webhook_token = a46a610c-4c52-4abf-bcad-5fde5cd545e4
LALIVE_webhook_token = 3f9379c7-ebcd-4844-9c61-3f2642f64df4
H2R_webhook_token = 3f9379c7-ebcd-4844-9c61-3f2642f64df4
CC_webhook_token = 3f9379c7-ebcd-4844-9c61-3f2642f64df4
ASTWB_webhook_token = 31ab2e27-df7c-46d6-8294-afb98a68b6f0
ASOPE_webhook_token = 31ab2e27-df7c-46d6-8294-afb98a68b6f0
ASTOP_webhook_token = 31ab2e27-df7c-46d6-8294-afb98a68b6f0
ES_webhook_token = 84b165e1-160f-4ebd-a36d-d1873cca0d20
SCM_webhook_token = 84b165e1-160f-4ebd-a36d-d1873cca0d20
UBRD_webhook_token = a46a610c-4c52-4abf-bcad-5fde5cd545e4
DBSYNC_webhook_token = 054f4c3c-b38b-46f8-9cc4-d9660e205fc7
ODS_webhook_token = 054f4c3c-b38b-46f8-9cc4-d9660e205fc7
AUTOMATION_webhook_token = ceb41bf3-f3ce-433f-bbea-2960bf46a819
; TO-RD_webhook_token = d8d9ac4b-e5f9-47a6-8810-0ae6b621a7cb
TO-RD_webhook_token = 2387b345-3675-49dd-971a-2f81b98e3ed1
TO-FE_webhook_token = 1d0dac58-dd68-412d-a2eb-b445927339a
GUE-RD_webhook_token = 8951949c-e300-4c96-a725-53346309851a
USER-FE_webhook_token = 4f946e2b-0b58-4eb5-aa22-3d3516aee1ba
SCM-RD_webhook_token = 74ceedc8-510f-4efd-bf64-4c5e6e87dc0f
SCM-monitor_webhook_token = b821c1e5-63b0-42c9-b7ff-ab89c6dcd076
PB_webhook_token = 84b165e1-160f-4ebd-a36d-d1873cca0d20
INFO_webhook_token = 20114e9a-9f09-44f9-bfbe-0da382745cde
CODING_webhook_token = 20114e9a-9f09-44f9-bfbe-0da382745cde
OFFLINE_webhook_token = 20114e9a-9f09-44f9-bfbe-0da382745cde
陈林 = ou_69254b2555a2c6257d5ca45d885f785e
吴勇刚 = ou_94f57439f58bde9376189a3cabb0b11a
陈慧宗 = ou_bc2b266b05a428959bfcff3378af2d36
胥雯筠 = ou_04e4a3ccf680acc17f1039dd57349a00
陈江 = ou_02ce6ebcb8a510c0b4102e15aaf59d05
谯新久 = ou_c3c6f7ac7997000dcb23e634a830851d
罗洪 = ou_f6981cb788d066accae770fda5fa7ee5
刘睿权 = ou_218303e87f4c45ff81ed16a1e1d0b1ee
陈典模 = ou_0fe84f418974af89e89148e2380427dd
李聪 = ou_711ebaac08461ae9a2726fcbe393d7de
左其灵 = ou_cc27134156714d88da23e159ba8390e1
宋飞飞 = ou_ccafa6ebe0194e42ea55f8804734e6d4
付文龙 = ou_589b7151ac583ca3d94f0fb2d579f282
赵晓放 = ou_a62ef7960b9c57e1946f55a6bb5d8b35
张浩东 = ou_04ba397251c63036e9d75096f3c28704
王玉杰 = ou_e3198cbf59327bac7dd686a94af30c2a
冯天 = ou_3522b9c4015645ea2d4c36aa6c260e90
郭松超 = ou_6a8e6ccaceb51167c4745b347085edd9
张雄 = ou_1ab263e2e7386fd5c5ecd67c1c6f2b6d
刘学刚 = ou_a17ade671a3030f12ae0fa13cc4f82bc
朱亮 = ou_0cc7a4c1a97bb58dd188b6aac6c7569b
高志军 = ou_5df6937cc109565f92868f0cdca4906c
马锦程 = ou_d35f2668f960300ee2d9a7768c7de9ce
刘英杰 = ou_d62d587ce3960add08214726897c228d
张晓刚 = ou_320976aaeb1e50976932f2eb446918c7
赵飞 = ou_17fdcc65d985a21a225034db318308bf
刘信林 = ou_dec1cf51c8b3e63823e7262928b20dbb
叶飞 = ou_3559e23c390a60a59f4b10e7053e5551
向明 = ou_4c26b3066017f57f6f2658b49e16b8a5
姜浩 = ou_8014499bb65adda6399ebc82e1a46724
姜灵敏 = ou_e4e113c845e88a43125c8a994710ff94
徐长乐 = ou_54c5d0f98d26ab7500a5af64d9022b1b
卿晨 = ou_3e517c22dbcdfce4b4e75af20a7e5d8c
李柏成 = ou_e40510cf340bc5ac0f860ac01844f1a1
苟宇恒 = ou_d82652fb433a470d7ac72a542292cd3b
董吉祥 = ou_4d9d1b8cf8c743f75313863a3ac336ad
白杨 = ou_ac886a59ffd1a4a0da4cb3d06c1e7b03
李明泽 = ou_0fc5af64989a1677fa4c34b7542e4c2c
沈佳坤 = ou_36ed516d812a1bacc3f978179e32910d
朱乾元 = ou_31a10ecba1074d45df6c6cbb13ec16e9
顾洋 = ou_543fe7b50e1cadf6974e93ac8663b09d
吴优 = ou_117b8d43a8122021d290ef89cbcb9c88
王亚超 = ou_649286a2b30bfe81bb4041a2b617fd80
徐佳林 = ou_f382da52d42932870a0ea122753f795a
彭霞 = ou_9ef2d9e4a5c098ab27933208dd95ff8a
通用 = ou_abcdefghijklmnopqrstuvwxyz123456
韩东堂 = ou_4ff52d71cb3cbe7cd1f26b2ac7da5eb0
邓振博 = ou_994af7ca097ea1b63a5bec13b0b9a42c
朱子朋 = ou_8d13121890e1de3851c0616b8392cb1f
李代军 = ou_279bbc6d368cffa743c959d7461d8481
黄海峰 = ou_8a4cf96dfc173dbed704d44fdc8fc5c1
杨文磊 = ou_885d10cea3c3ae72c380971a7082da06
卢纪霖 = ou_eaaf66ef5c53d3977a0bd3961376beae
杨远宁 = ou_8693a6f29fe174b4d18155e6d4da0396
崔瑞 = ou_7bbb7b678b07e74fd1c245bafc6877f9
李振宇 = ou_82a619af1d592dfb994ee28389815ee9
王同刚 = ou_ca8ea6ff6bf6258f49932dc4b0c98fbc
张瑞涛 = ou_ca355f3fad90fe934087d225bb5794b4
左钊 = ou_ea9a364cffa235aec88d37d075e300bb
田翔 = ou_75796572af14c996f853f015ecb788e0
赵加会 = ou_33ed201f4e7f0f8919bc7021130c066e
刘欣畅 = ou_6247db29c46ac536d2aa10fab9711a82
田文昌 = ou_60e01001facaadd78b89ef05281e0763
郑宇翔 = ou_4e8d677a2389a8f8a5517961f2423970
王国静 = ou_862b663bac7be9762eb78d71a9e6defb
李其 = ou_cfb094d4d15f7da729eea8b1491ffd13
尚万中 = ou_53e6edbf2ef8a1620cbadb4b7efeb966
张景峰 = ou_48c88a2ff129e5d23215d245c6b82fc0
[user_name]
fuwenlong = 付文龙
fengtian = 冯天
guosongchao = 郭松超
wangyujie02 = 王玉杰
zhanghaodong = 张浩东
zhuliang = 朱亮
liuxuegang = 刘学刚
gaozhijun = 高志军
majincheng = 马锦程
zhaoxiaofang = 赵晓放
liuyingjie = 刘英杰
zhangxiaogang = 张晓刚
zhaofei = 赵飞
liuxinlin = 刘信林
yefei = 叶飞
xiangming = 向明
jianghao = 姜浩
jianglingmin = 姜灵敏
xuchangle = 徐长乐
qingchen = 卿晨
libaicheng = 李柏成
gouyuheng = 苟宇恒
jixiang.dong = 董吉祥
baiyang01 = 白杨
wuyonggang = 吴勇刚
xuwenjun = 胥雯筠
songfeifei = 宋飞飞
wanggang02 = 王刚
lichao04 = 李超
xiexiangyi = 谢祥益
denghaiou = 邓海鸥
yaokun = 姚坤
chenhuizong = 陈慧宗
yuanzhengqi = 袁正旗
luohong = 罗洪
chendianmo = 陈典模
licong = 李聪
zuoqiling = 左其灵
zhengxin01 = 郑新
handongtang = 韩东堂
dengzhenbo = 邓振博
zhuzipeng = 朱子朋
lidaijun = 李代军
huanghaifeng =黄海峰
yangwenlei01 =杨文磊
lujilin = 卢纪霖
yangyuanning = 杨远宁
cuirui = 崔瑞
lizhenyu = 李振宇
wangtonggang = 王同刚
zhangruitao01 = 张瑞涛
zuozhao = 左钊
tianxiang = 田翔
zhaojiahui = 赵加会
liuxinchang = 刘欣畅
tianwenchang = 田文昌
zhengyuxiang = 郑宇翔
zhangxiong = 张雄
wangguojing = 王国静
liqi03 = 李其
shangwanzhong = 尚万中
zhangjingfeng = 张景峰
limingze = 李明泽
zhuqianyuan = 朱乾元
guyang = 顾洋
shenjiakun = 沈佳坤
wuyou = 吴优
[dingding_user_list]
SCM_owner = 13350956802
景虎成 = 13350956802
文妮 = 17302811505
罗洪 = 13550629276
TO_owner = 18215530124
李超 = 18011454607
吴勇刚 = 13540133074
王刚 = 19141999584
刘明浩 = 18380450039
谢祥益 = 18010623985
唐其麟 = 18011454607
肖淇迈 = 19141999584
唐浩 = 18380450039
TMO_owner = 18215530124
姚坤 = 18215530124
罗志鹏 = 18582482272
刘涛婷 = 18328504751
陈江 = 13458500234
ASTWB_owner = 18202810506
谯新久 = 18202810506
刘鹏 = 13547858402
杨雨 = 13608006659
华学敏 = 13708231975
ASORG_owner = 18202810506
PZ_owner = 18202810506
肖亮 = 18108096240
ASTOP_owner = 18780106567
ASOPE_owner = 18180956201
蒋安龙 = 18180956201
杨中莲 = 13882105134
CC_owner = 18215530124
林于棚 = 18782019436
黄业宏 = 18603267203
EN_owner = 18180956201
LALIVE_owner = 18215530124
H2R_owner = 18380448416
胥雯筠 = 18780106567
周仁华 = 18281029023
袁正旗 = 18030895120
刘睿权 = 15681935823
文青 = 18584851102
刘德全 = 13402829590
包利 = 18380448416
ASTOP_to_dd = 495fc7e0171e829acda91ffa08eaf37307e123fafc090249b633eb651c31dd79
ASTWB_to_dd = 2079cb3e311ab6a37138a6d4671181661d211c12a1532c2012ae7e6b397996c3
ASOPE_to_dd = 147617cea7e1b480e54cecb6dfd33edb5a9ed872e1bab52007d488673ad8ab0f
ASORG_to_dd = 2079cb3e311ab6a37138a6d4671181661d211c12a1532c2012ae7e6b397996c3
CC_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
H2R_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
LALIVE_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
TMO_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
TO_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
UBRD_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
ULS_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
SCM_to_dd = 38fc76120cb204e2c1da53ec9921805670466da64c37aed8bc6fba0513f5688b

View File

@@ -0,0 +1,851 @@
# -*- coding:utf-8 -*-
import copy
import importlib
import json
import os
import random
import re
import sys
import string
import time
import traceback
def get_project_root_path(path="base_framework"):
"""
获取项目目录
"""
o_path = os.getcwd()
try:
project_path = re.search(r"(.*%s)" % path, o_path).group(1)
return os.path.abspath(os.path.join(project_path, os.path.pardir))
except:
pass
Project_Path = get_project_root_path()
sys.path.append(Project_Path)
from base_framework.platform_tools.Keywords_service.keyword_service import SetAttr, log
def load_module_from_file(file_path, team):
module_file = re.search(r"\\(%s\\.*)\.py" % team, file_path)
# if not module_file:
# module_file = re.search(r"\\(%s\\.*)\.py" % team.lower(), file_path)
temp = module_file.group(1)
temp = temp.replace(os.sep, ".")
# try:
# module = importlib.import_module(temp)
# except Exception as err:
# temp = re.sub(r"%s\." % team, "%s." % team.lower(), temp)
# module = importlib.import_module(temp)
return importlib.import_module(temp)
class ITCaseFileOperation(object):
def __init__(self, team, kw_instance):
self.kw = kw_instance
self.all_url = kw_instance.all_url
self.kw_class_name = kw_instance.kw_class
self.case_dir = os.path.abspath(os.path.join(Project_Path, team, "test_case", "TestCase", "1.接口"))
self.env_path = os.path.abspath(
os.path.join(Project_Path, team, "test_case", "Resource", "AdapterKws", "env.robot"))
def _check_case_file_exist(self, file_path):
if os.path.exists(file_path) and os.path.isfile(file_path):
return True
else:
return False
def check_case_exist(self, case_path, case_name):
if self._check_case_file_exist(case_path):
with open(case_path, "r", encoding="utf-8") as f:
for line in f.readlines():
if line.startswith(case_name):
return True
else:
return False
else:
return False
def generate_case_file(self, file_path):
dirname = file_path[:-len(os.path.basename(file_path))]
if not self._check_case_file_exist(file_path):
if not os.path.exists(dirname):
os.makedirs(dirname)
self.write_content_to_case_file(file_path, self.generate_env_releate_path(dirname))
else:
with open(file_path, "r", encoding="utf-8") as f:
content = f.readlines()
if content:
if "\n" != content[-1]:
self.write_content_to_case_file(file_path, "\n")
else:
self.write_content_to_case_file(file_path, self.generate_env_releate_path(dirname))
def generate_env_releate_path(self, file_path):
return "*** Settings ***\nResource " + os.path.relpath(self.env_path,
file_path).replace("\\",
"/") + "\n\n*** Test Cases ***\n"
def _check_case_file_resource(self, file_path):
"""
确定case文件中是否有关键字
"""
with open(file_path, "a+", encoding="utf-8") as f:
content_list = f.readlines()
if "*** Keywords ***\n" not in content_list:
return True
else:
kw_index = content_list.rindex("*** Keywords ***\n")
for index in range(kw_index, len(content_list) + 1):
if "*** Test Cases ***\n" == content_list[index]:
status = True
if "*** Keywords ***\n" == content_list[index]:
status = False
return status
def write_content_to_case_file(self, file_path, content):
status = self._check_case_file_resource(file_path)
with open(file_path, "a+", encoding="utf-8") as f:
if status:
f.write(content)
else:
content = "*** Test Cases ***\n" + content
f.write(content)
class ITCaseContentOperation(object):
def __init__(self, team, kw_instance):
self.team = team
self.file = ITCaseFileOperation(team, kw_instance)
self.doc_content_row_temp = " ... | {} | {} | {} |"
self.normal_1001 = None
self.un_expect_list = None
self.case_string = None
def _generate_case_name(self, interface_name, case_type, case_des):
return "-".join([interface_name, case_type, case_des])
def _check_interface_confirm(self, interface_info):
if not interface_info.confirmed:
raise Exception("接口%s未确认,请先确认接口参数。" % interface_info.name)
def generate_case_body_all_1001_array(self, request_demo, parameters):
temp_list = list()
demo_copy = copy.deepcopy(request_demo)
for key, value in request_demo.items():
if not value:
demo_copy.pop(key)
if len(demo_copy.keys()) == 1:
demo_copy = demo_copy.pop(list(demo_copy.keys())[0])
dict_para, self.un_expect_list, _, _ = self._analysis_case_parameters_to_dict(parameters)
demo_add_values = self._analysis_case_parameters_to_value(demo_copy, dict_para)
data_list = self._analysis_dict_to_body_value(demo_copy, demo_add_values)
if isinstance(demo_copy, list):
for item in data_list:
temp_list.append([item])
data_list = temp_list
return data_list
def _generate_parameter_to_dict(self, parameters):
dict_temp = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
dict_temp[parameter_attr.name] = parameter_attr
return dict_temp
def _analysis_case_parameters_to_value_empty_dict(self, request_demo, parameters, type=None):
"""
循环生成缺少某个参数的值,其它正确的参数组合列表
"""
parameter_expect_list = list()
expect_lenth = 0
un_expect_lenth = 0
parameter_expect_dict = dict()
parameter_un_expect_dict = dict()
for key, parameter_attr in parameters.items():
if parameter_attr.normal_values:
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
parameter_expect_dict[parameter_attr.name] = [
self._generate_body_expect_type_value(parameter_attr.type)]
if len(parameter_expect_dict[parameter_attr.name]) > expect_lenth:
expect_lenth = len(parameter_expect_dict[parameter_attr.name])
if parameter_attr.exception_values:
parameter_un_expect_dict[parameter_attr.name] = parameter_attr.exception_values.split(",")
else:
parameter_un_expect_dict[parameter_attr.name] = [
self._generate_body_un_expect_type_value(parameter_attr.type)]
if len(parameter_un_expect_dict[parameter_attr.name]) > un_expect_lenth:
un_expect_lenth = len(parameter_un_expect_dict[parameter_attr.name])
for key in parameter_expect_dict.keys():
if key not in json.dumps(request_demo):
continue
value = copy.copy(parameter_expect_dict)
value[key] = ['NULL']
if parameters.get(key).is_need == 1:
status = "FAIL"
else:
status = "PASS"
if type == "1102":
name = "缺少参数%s" % key
else:
name = "缺少参数%s的值" % key
parameter_expect_list.append([value, status, name])
return parameter_expect_list
def _distinguish_parameters_by_is_needed(self, parameters):
"""
将必填参数和非必须参数分开成2个列表
"""
need_parameters = list()
no_need_parameters = list()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
if parameter_attr.is_need != 0:
need_parameters.append(parameter_attr)
else:
no_need_parameters.append(parameter_attr)
return need_parameters, no_need_parameters
def _replace_null_value_no_need_parameters(self, demo, no_need_parameter_attrs):
"""
将所有非必填参数置空
"""
really_body = copy.deepcopy(demo)
for parameter_attr in no_need_parameter_attrs:
if re.search(r"\"%s\"" % parameter_attr.name, demo):
check_para = r"\"%s\":\s[\[\{]" % parameter_attr.name
if re.search(check_para, demo):
continue
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
replace1 = "\"%s\": \"NULL\"" % parameter_attr.name
really_body = re.sub(patten1, replace1, really_body)
return really_body
def generate_case_body_all_1101_array(self, request_demo, parameters):
"""
生成1101用例对应的所有请求参数组合
"""
body_array = list()
need, no_need = self._distinguish_parameters_by_is_needed(parameters)
demo_json = json.dumps(request_demo, ensure_ascii=False)
if no_need:
no_need_demo_json = self._replace_null_value_no_need_parameters(demo_json, no_need)
body_array.append([json.loads(no_need_demo_json), "PASS", "所有非必填参数为空"])
for item in need:
parameter_attr = copy.copy(item)
demo_json_copy = copy.copy(demo_json)
if re.search("\"%s\"" % parameter_attr.name, demo_json_copy):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json_copy):
continue
name = "缺少参数%s" % parameter_attr.name
log.info("1101%s" % name)
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
replace1 = "\"%s\": []" % parameter_attr.name
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = "\"%s\": \"NULL\"" % parameter_attr.name
really_body = re.sub(patten1, replace1, demo_json_copy)
if parameter_attr.is_need == 1:
status = "FAIL"
else:
status = "PASS"
body_array.append([json.loads(really_body), status, name])
return body_array
def _clean_all_none_json_char(self, really_body):
"""
清除1102场景删除缺少参数后不规则的字符串内容
"""
really_body = re.sub(",\s,", ",", really_body)
really_body = re.sub("\[\s?,", "[", really_body)
really_body = re.sub("\{\s?,", "{", really_body)
really_body = re.sub(",\s\]", "]", really_body)
really_body = re.sub(",\s\}", "}", really_body)
really_body = re.sub("\s,", "", really_body)
return really_body
def _replace_null_all_no_need_parameters(self, demo, no_need_parameter_attrs):
"""
将所有非必填参数置空
"""
really_body = copy.deepcopy(demo)
for parameter_attr in no_need_parameter_attrs:
if re.search(r"\"%s\"" % parameter_attr.name, demo):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo):
continue
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
replace1 = ""
really_body = re.sub(patten1, replace1, really_body)
really_body = self._clean_all_none_json_char(really_body)
return really_body
def generate_case_body_all_1102_array(self, request_demo, parameters):
"""
生成1102用例对应的所有参数组合
"""
body_array = list()
need, no_need = self._distinguish_parameters_by_is_needed(parameters)
demo_json = json.dumps(request_demo, ensure_ascii=False)
if no_need:
no_need_demo_json = self._replace_null_all_no_need_parameters(demo_json, no_need)
body_array.append([json.loads(no_need_demo_json), "PASS", "所有非必填参数均缺失"])
for item in need:
parameter_attr = copy.copy(item)
demo_json_copy = copy.copy(demo_json)
if re.search(r"\"%s\"" % parameter_attr.name, demo_json_copy):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json_copy):
continue
name = "缺少参数%s" % parameter_attr.name
log.info("1102%s" % name)
expect_value = parameter_attr.normal_values.split(",")[0]
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = ""
really_body = re.sub(patten1, replace1, demo_json_copy)
really_body = self._clean_all_none_json_char(really_body)
if parameter_attr.is_need == 1:
status = "FAIL"
else:
status = "PASS"
body_array.append([json.loads(really_body), status, name])
return body_array
def generate_case_body_all_1201_array(self, request_demo, parameters):
"""
生成1201用例对应的所有参数组合
"""
body_array = list()
demo_json = json.dumps(request_demo, ensure_ascii=False)
for item in parameters:
parameter_attr = SetAttr(item)
if re.search(r"\"%s\"" % parameter_attr.name, demo_json):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json):
continue
log.info("参数%s开始替换异常值" % parameter_attr.name)
for un_expect_item in self.un_expect_list.get(parameter_attr.name, []):
name = "参数%s使用异常值%s" % (parameter_attr.name, un_expect_item)
demo_json_copy = copy.copy(demo_json)
expect_value = parameter_attr.normal_values.split(",")[0]
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
replace1 = '"%s": ["%s"]' % (parameter_attr.name, un_expect_item)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = '"%s": "%s"' % (parameter_attr.name, un_expect_item)
really_body = re.sub(patten1, replace1, demo_json_copy)
status = "FAIL"
body_array.append([json.loads(really_body), status, name])
return body_array
def create_case_content(self, result_path, interface_info, parameters, demo, case_type, tags=None):
"""
生成用例的所有内容
"""
self._check_interface_confirm(interface_info)
body_content = list()
doc = self.generate_case_doc(interface_info, tags=tags, demo=demo)
case_file, _ = self.generate_case_file_path(interface_info)
interface_name = interface_info.name
if not parameters:
body = self.generate_case_body_content(interface_info.in_url, doc, interface_name, case_type,
interface_info.interface_describe, None, status="PASS")
else:
try:
body_array = self.generate_case_body_all_1001_array(demo, parameters)
self.normal_1001 = body_array[0]
except TypeError:
log.warning(traceback.print_exc())
body_array = []
self.normal_1001 = {}
if case_type == "1001":
try:
body = self.generate_case_body_content(interface_info.in_url, doc, interface_name, case_type,
interface_info.interface_describe, body_array, status="PASS")
except Exception as err:
msg = "生成用例%s,%s失败原因如下:%s" % (interface_info.type, interface_info.in_url, traceback.print_exc())
log.error(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
elif case_type == "1101" or case_type == "1102" or case_type == "1201":
body_temp_list = []
# parameters_temp = self._generate_parameter_to_dict(parameters)
try:
body_array = eval(
"self.generate_case_body_all_%s_array(self.normal_1001, parameters)" % case_type)
if case_type == "1101":
name = "缺少参数值"
elif case_type == "1102":
name = "缺少参数key和值"
elif case_type == "1201":
name = "参数使用异常值"
else:
name = ""
body_tmp = self.generate_case_body_content(interface_info.in_url, doc, interface_name,
case_type,
name, body_array, status="PASS")
body_temp_list.append(body_tmp)
body = "".join(body_temp_list)
except Exception as err:
msg = "生成用例%s,%s失败原因如下:%s" % (interface_info.type, interface_info.in_url, traceback.print_exc())
log.error(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
raise err
body_content.append(body)
self.file.write_content_to_case_file(case_file, "\n".join(body_content))
msg = "接口url%s,%s,生成case%s成功!路径为:%s" % (interface_info.type, interface_info.in_url, case_type, case_file)
log.info(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
return msg
def generate_case_body_content(self, url, doc, interface_name, case_type, description, body_para, status="PASS"):
body_content = list()
case_name = self._generate_case_name(interface_name, case_type, description)
body_content.append(case_name)
body_content.append(doc)
# 如何需要自动生成1001类型用例的请求body数据请将304/305行放开并把306~310注释
request = self._generate_case_body_content(url, body_content=body_para, status=status)
body_content.append(request)
# if case_type != "1001":
# request = self._generate_case_body_content(url, body_content=body_para, status=status)
# body_content.append(request)
# else:
# body_content.append(" #请自行添加用例请求数据!\n")
return "\n".join(body_content)
def _generate_case_body_content(self, url, body_content=None, status="PASS"):
body_list = []
if body_content is None:
try:
body_request = " ${resp} %s" % (self.file.all_url.get(url)[1])
except Exception as err:
log.error(traceback.print_exc())
body_request = " #url: %s 无法找到对应的关键字,请检查并更新!" % url
body_list.append(body_request)
body_list.append(" Log ${resp}")
if self.team.upper() in ["TMO"]:
body_validate_success = " should be equal as json ${resp} %s" % '请自行添加检查结果'
else:
body_validate_code = " Should Be Equal ${resp['code']} ${200}"
body_validate_success = " Should Be True ${resp['success']}"
body_list.append(body_validate_code)
body_list.append(body_validate_success)
else:
if not body_content:
body_create = " #request_demo无内容或者parameters在数据表中不全请先检查再生成 \n ${body} Evaluate %s" % body_content
body_list.append(body_create)
count = 0
for real_body in body_content:
body_error = None
result = {"message": "", "code": 200, "success": True}
temp = copy.copy(real_body)
if len(real_body) == 3 and isinstance(real_body, list):
if real_body[1] == "PASS" or real_body[1] == "FAIL":
real_body = temp[0]
status = temp[1]
case_description = temp[2]
body_list.append(" #%s" % case_description)
try:
module = load_module_from_file(self.file.kw.kw_file_path, self.team)
importlib.reload(module)
if isinstance(real_body, list):
temp_type = "*"
else:
temp_type = "**"
result = eval(
"%s.%s().%s(%sreal_body)" % (
"module", self.file.kw_class_name, self.file.all_url.get(url)[1], temp_type))
except:
msg = "#url: %s 无法通过参数得到请求结果!" % url
log.warning(real_body)
log.warning(msg)
log.warning(traceback.print_exc())
if "${time}" in str(real_body):
body_list.append(" ${time} get time epoch")
body_create = " ${body_%s} Evaluate %s" % (count, real_body)
try:
if isinstance(real_body, list):
body_request = " ${resp_%s} %s %s" % (
count, self.file.all_url.get(url)[1], "@{body_%s}" % count)
elif isinstance(real_body, dict):
body_request = " ${resp_%s} %s %s" % (
count, self.file.all_url.get(url)[1], "&{body_%s}" % count)
except Exception as err:
log.error(traceback.print_exc())
body_request = " #url: %s 无法找到对应的关键字,请检查并更新!" % url
body_list.append(body_create)
body_list.append(body_request)
body_list.append(" Log ${resp_%s}" % count)
if self.team.upper() in ["TMO"]:
body_validate_resp = " Should Be Equal as json ${resp_%s} %s" % (count, result)
else:
try:
body_validate_code = " Should Be Equal as strings ${resp_%s['code']} ${%s}"
# body_validate_success = " %s ${resp_%s['success']}"
body_validate_success = " %s ${resp_%s['success']} %s"
body_validate_resp = " %s ${resp_%s[\"message\"]} %s"
if isinstance(result, str):
body_validate_code = " #特殊接口返回!"
body_validate_success = " #返回为bytes类型的字符码。仅检查结果是否相等"
body_validate_resp = " Should Be Equal as strings ${resp_%s} %s" % (count, result)
elif status == "PASS":
body_validate_code = body_validate_code % (count, result['code'])
body_validate_success = body_validate_success % ("Should Be True", count, "")
try:
if result["message"]:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, result["message"])
else:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, '${EMPTY}')
except KeyError as err:
body_validate_resp = " #resp中没有message请后续自行添加"
log.warning(err)
log.warning("resp中没有message请后续自行添加")
else:
try:
body_validate_code = body_validate_code % (count, result['code'])
except Exception as e:
if "status" in result:
body_validate_code = body_validate_code % (count, result['status'])
body_validate_code = body_validate_code.replace("code", "status")
else:
raise KeyError("接口不中无code返回请与开发确认")
try:
body_validate_success = body_validate_success % (
"Should Be Equal As Strings", count, result["success"])
except Exception as err:
if "error" in result:
body_validate_success = body_validate_success % (
"Should Be Equal As Strings", count, result["error"])
body_validate_success = body_validate_success.replace("success", "error")
else:
raise KeyError("接口不中无success返回请与开发确认")
try:
if result.get("message"):
if re.search("[a-zA-Z0-9]{32}", result.get("message", "")):
body_validate_resp = body_validate_resp % ("should contain", count,
re.sub("[a-zA-Z0-9]{32}", "", result["message"]))
else:
body_validate_resp = body_validate_resp % (
"should be equal as strings", count, result["message"])
else:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, '${EMPTY}')
except KeyError as err:
body_validate_resp = " #resp中没有message请后续自行添加"
log.warning(err)
log.warning("resp中没有message请后续自行添加")
except Exception as error:
log.error(error)
body_validate_code = None
body_validate_success = None
body_validate_resp = None
body_error = " #无法正确生成异常用例返回值,请检查并手动生成!"
if body_validate_code:
body_list.append(body_validate_code)
if body_validate_success:
body_list.append(body_validate_success)
if body_validate_resp:
body_list.append(body_validate_resp)
if body_error:
body_list.append(body_error)
# body_list.append(" #请自行添加data校验")
count += 1
body_list.append("\n")
return "\n".join(body_list)
def _generate_body_expect_type_value(self, type):
if type.lower() == 'integer':
value = random.randint(0, 100)
elif type.lower() == "string":
value = "".join([random.choice(string.digits + string.ascii_letters) for i in range(5)])
elif type.lower() == "date":
value = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
elif type.lower() == "boolean":
value = True
elif type.lower() == "array":
value = [1]
else:
value = "123"
return value
def _generate_body_un_expect_type_value(self, type):
value = ""
if type.lower() == 'integer':
value = "".join([random.choice(string.digits + string.ascii_letters) for i in range(5)])
elif type.lower() == "string":
value = random.randint(0, 10000)
else:
value = False
return value
def _analysis_dict_to_body_value(self, dict_none, dict_data):
"""
生成所有请求参数的组合列表
"""
data_list = []
if isinstance(dict_none, list):
if dict_none:
for index in range(len(dict_none)):
data = self._analysis_dict_to_body_value(dict_none[index], dict_data[index])
data_list.extend(data)
else:
data_list = dict_data
elif isinstance(dict_none, dict):
try:
len_num = self._get_nums(dict_data)
except Exception as e:
# log.error(e)
log.error("数据表request_parameters中参数不全请使用interface_hunter补全参数")
raise e
for i in range(0, len_num):
copy_dict = copy.copy(dict_none)
for key, value in copy_dict.items():
data = self._analysis_dict_to_body_value(copy_dict[key], dict_data[key])
if not data:
copy_dict[key] = None
elif i >= len(data):
copy_dict[key] = data[0]
if isinstance(value, list):
copy_dict[key] = [data[0]]
else:
copy_dict[key] = data[i]
if isinstance(value, list):
copy_dict[key] = [data[i]]
data_list.append(copy_dict)
else:
data_list = dict_data
return data_list
def _get_nums(self, dict_data):
"""
获取所有参数对应值的最大长度
"""
nums_list = [0]
if isinstance(dict_data, list):
for item in dict_data:
if isinstance(item, dict):
data = self._get_nums(dict_data[0])
nums_list.append(data)
else:
nums_list.append(len(dict_data))
elif isinstance(dict_data, dict):
for value in dict_data.values():
data = self._get_nums(value)
nums_list.append(data)
elif dict_data == None:
nums_list.append(0)
else:
nums_list.append(len(dict_data))
return max(nums_list)
def _analysis_case_parameters_to_value(self, interface_demo, parameters):
"""
根据demo将每个参数的对应值的列表写回demo
"""
body = copy.copy(interface_demo)
if isinstance(body, list):
for index in range(len(body)):
item = body[index]
body[index] = self._analysis_case_parameters_to_value(item, parameters)
elif isinstance(body, dict):
for key, value in body.items():
if isinstance(value, dict) or isinstance(value, list):
if value:
body[key] = self._analysis_case_parameters_to_value(body.get(key), parameters)
else:
body[key] = parameters.get(key)
else:
body[key] = parameters.get(key)
return body
def _analysis_case_parameters_to_dict(self, parameters):
"""
根据所有参数生成正常值 和异常值的列表
"""
expect_lenth = 0
un_expect_lenth = 0
parameter_expect_dict = dict()
parameter_un_expect_dict = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
if parameter_attr.normal_values:
if parameter_attr.normal_values.startswith("["):
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
if parameter_attr.type == "array":
parameter_expect_dict[parameter_attr.name] = [[x] for x in parameter_attr.normal_values.split(",")]
else:
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
continue
if len(parameter_expect_dict[parameter_attr.name]) > expect_lenth:
expect_lenth = len(parameter_expect_dict[parameter_attr.name])
if parameter_attr.exception_values:
parameter_un_expect_dict[parameter_attr.name] = parameter_attr.exception_values.split(",")
else:
# raise Exception("接口参数%s无异常值请先确认接口参数。" % parameter_attr.name)
parameter_un_expect_dict[parameter_attr.name] = []
if len(parameter_un_expect_dict[parameter_attr.name]) > un_expect_lenth:
un_expect_lenth = len(parameter_un_expect_dict[parameter_attr.name])
return parameter_expect_dict, parameter_un_expect_dict, expect_lenth, un_expect_lenth
def generate_case_doc(self, interface_info, tags=None, demo=None):
if demo:
lenth, demo_json, paras_d = self.file.kw._clean_demo_to_less_key(demo)
doc_list = list()
doc_list.append(
" [Documentation] {} 接口路径:{}, {}".format(interface_info.interface_describe,
interface_info.type, interface_info.in_url))
# doc_list.append(self.doc_content_row_temp.format("输入参数名", "参数类型", "说明"))
# for parameters_info in parameters:
# sub_doc = self._generate_case_doc(parameters_info)
# doc_list.append(sub_doc)
if tags:
tag_list = tags.split(",")
tag = " [Tags]"
for tag_ in tag_list:
tag += " %s" % tag_
doc_list.append(tag)
if lenth > 1:
doc_list.append(
" # 请求参数说明 h:放header里的参数 u:放url路径节点中的参数 d:不放在url中在请求参数 p放在url问号后在key-value结构参数")
return "\n".join(doc_list)
def _generate_case_doc(self, request_parameter):
doc_content = list()
parameter_attr = SetAttr(request_parameter)
row = self.doc_content_row_temp.format(parameter_attr.name, parameter_attr.type, parameter_attr.note)
doc_content.append(row)
return "\n".join(doc_content)
def generate_case_file_path(self, interface_info):
path_cell_list = self._generate_case_file_path_info(interface_info.in_url)
path_cell_list.insert(0, self.file.case_dir)
return os.path.abspath(os.path.join(*path_cell_list[:-1]) + ".robot"), path_cell_list[-1]
def _generate_case_file_path_info(self, url):
head_list = []
para_list = []
host, url_last = re.search(r"(\S+\.cn|\S+\.com)(\S+)", url).groups()
path_first = re.search("//([\w+\-]+)\.", host).group(1)
head_list.append(path_first)
search = re.findall("[\w+\-_]+|[\{\w+\-_\}]+", url_last)
for item in search:
if "{" in item:
temp = item.replace("{", "").replace("}", "")
para_list.append(temp)
else:
head_list.append(item)
return head_list
def run_interface_case_generate(result_path, interface, demo_info, parameters, case_instance, tags=None, normal=None):
case_type_list = ["1001", "1101", "1102", "1201"]
not_exist_case_type = list()
exist_case_name = list()
case_path, _ = case_instance.generate_case_file_path(interface)
case_name_head = interface.name
if parameters:
for case_type in case_type_list:
case_name = "-".join([case_name_head, case_type])
if not case_instance.file.check_case_exist(case_path, case_name):
not_exist_case_type.append(case_type)
else:
exist_case_name.append(case_name)
else:
case_name = "-".join([case_name_head, "1001"])
if not case_instance.file.check_case_exist(case_path, case_name):
not_exist_case_type.append("1001")
else:
exist_case_name.append(case_name)
for item in exist_case_name:
msg = "接口url%s,%s,已存在用例%s,请确认!" % (interface.type, interface.in_url, item)
log.warning(msg)
case_instance.file.write_content_to_case_file(result_path, msg + "\n")
if not_exist_case_type:
if normal:
not_exist_case_type = ["1001"]
case_instance.file.generate_case_file(case_path)
try:
demo = json.loads(demo_info.request_demo)
except Exception as err:
log.error(traceback.print_exc())
log.warning("the demo of interface(%s) is empty,please check." % interface.id)
demo = dict()
try:
result = list()
result.append("")
result.append("-" * 60)
result.append("用例生成成功!")
for case_type in not_exist_case_type:
body = case_instance.create_case_content(result_path, interface, parameters, demo, case_type, tags=tags)
# result.append(body)
result.append(body.split("")[1])
log.info("\n".join(result))
except:
log.error("could not generate case for interface id: %s, url: %s" % (interface.id, interface.in_url))
log.error(traceback.print_exc())
else:
msg = "接口url%s,%s,已存在用例,请确认!" % (interface.type, interface.in_url)
log.warning(msg)
case_instance.file.write_content_to_case_file(result_path, msg + "\n")

View File

@@ -0,0 +1,833 @@
# -*- coding:utf-8 -*-
import copy
import importlib
import json
import os
import random
import re
import sys
import string
import time
import traceback
def get_project_root_path(path="base_framework"):
"""
获取项目目录
"""
o_path = os.getcwd()
try:
project_path = re.search(r"(.*%s)" % path, o_path).group(1)
return os.path.abspath(os.path.join(project_path, os.path.pardir))
except:
pass
Project_Path = get_project_root_path()
sys.path.append(Project_Path)
from base_framework.platform_tools.Keywords_service.keyword_service1 import SetAttr, log
def load_module_from_file(file_path, team):
module_file = re.search(r"(%s\\.*)\.py" % team, file_path)
if not module_file:
module_file = re.search(r"(%s.*)\.py" % team.lower(), file_path)
temp = module_file.group(1)
temp = temp.replace(os.sep, ".")
# try:
# module = importlib.import_module(temp)
# except Exception as err:
# temp = re.sub(r"%s\." % team, "%s." % team.lower(), temp)
# module = importlib.import_module(temp)
return importlib.import_module(temp)
class ITCaseFileOperation(object):
def __init__(self, team, kw_instance):
self.kw = kw_instance
self.all_url = kw_instance.all_url
self.kw_class_name = kw_instance.kw_class
self.case_dir = os.path.abspath(os.path.join(Project_Path, team, "test_case", "TestCase", "1.接口"))
self.env_path = os.path.abspath(
os.path.join(Project_Path, team, "test_case", "Resource", "AdapterKws", "env.robot"))
def _check_case_file_exist(self, file_path):
if os.path.exists(file_path) and os.path.isfile(file_path):
return True
else:
return False
def check_case_exist(self, case_path, case_name):
if self._check_case_file_exist(case_path):
with open(case_path, "r", encoding="utf-8") as f:
for line in f.readlines():
if line.startswith(case_name):
return True
else:
return False
else:
return False
def generate_case_file(self, file_path):
if not self._check_case_file_exist(file_path):
dirname = file_path[:-len(os.path.basename(file_path))]
if not os.path.exists(dirname):
os.makedirs(dirname)
self.write_content_to_case_file(file_path, self.generate_env_releate_path(dirname))
else:
with open(file_path, "r", encoding="utf-8") as f:
if f.readlines():
if "\n" != f.readlines()[-1]:
self.write_content_to_case_file(file_path, "\n")
def generate_env_releate_path(self, file_path):
return "*** Settings ***\nResource " + os.path.relpath(self.env_path,
file_path).replace("\\",
"/") + "\n\n*** Test Cases ***\n"
def _check_case_file_resource(self, file_path):
"""
确定case文件中是否有关键字
"""
with open(file_path, "a+", encoding="utf-8") as f:
content_list = f.readlines()
if "*** Keywords ***\n" not in content_list:
return True
else:
kw_index = content_list.rindex("*** Keywords ***\n")
for index in range(kw_index, len(content_list) + 1):
if "*** Test Cases ***\n" == content_list[index]:
status = True
if "*** Keywords ***\n" == content_list[index]:
status = False
return status
def write_content_to_case_file(self, file_path, content):
status = self._check_case_file_resource(file_path)
with open(file_path, "a+", encoding="utf-8") as f:
if status:
f.write(content)
else:
content = "*** Test Cases ***\n" + content
f.write(content)
class ITCaseContentOperation(object):
def __init__(self, team, kw_instance):
self.team = team
self.file = ITCaseFileOperation(team, kw_instance)
self.doc_content_row_temp = " ... | {} | {} | {} |"
self.normal_1001 = None
self.un_expect_list = None
self.case_string = None
def _generate_case_name(self, interface_name, case_type, case_des):
return "-".join([interface_name, case_type, case_des])
def _check_interface_confirm(self, interface_info):
if not interface_info.confirmed:
raise Exception("接口%s未确认,请先确认接口参数。" % interface_info.name)
def generate_case_body_all_1001_array(self, request_demo, parameters):
temp_list = list()
demo_copy = copy.deepcopy(request_demo)
for key, value in request_demo.items():
if not value:
demo_copy.pop(key)
if len(demo_copy.keys()) == 1:
demo_copy = demo_copy.pop(list(demo_copy.keys())[0])
dict_para, self.un_expect_list, _, _ = self._analysis_case_parameters_to_dict(parameters)
demo_add_values = self._analysis_case_parameters_to_value(demo_copy, dict_para)
data_list = self._analysis_dict_to_body_value(demo_copy, demo_add_values)
if isinstance(demo_copy, list):
for item in data_list:
temp_list.append([item])
data_list = temp_list
return data_list
def _generate_parameter_to_dict(self, parameters):
dict_temp = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
dict_temp[parameter_attr.name] = parameter_attr
return dict_temp
def _analysis_case_parameters_to_value_empty_dict(self, request_demo, parameters, type=None):
"""
循环生成缺少某个参数的值,其它正确的参数组合列表
"""
parameter_expect_list = list()
expect_lenth = 0
un_expect_lenth = 0
parameter_expect_dict = dict()
parameter_un_expect_dict = dict()
for key, parameter_attr in parameters.items():
if parameter_attr.normal_values:
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
parameter_expect_dict[parameter_attr.name] = [
self._generate_body_expect_type_value(parameter_attr.type)]
if len(parameter_expect_dict[parameter_attr.name]) > expect_lenth:
expect_lenth = len(parameter_expect_dict[parameter_attr.name])
if parameter_attr.exception_values:
parameter_un_expect_dict[parameter_attr.name] = parameter_attr.exception_values.split(",")
else:
parameter_un_expect_dict[parameter_attr.name] = [
self._generate_body_un_expect_type_value(parameter_attr.type)]
if len(parameter_un_expect_dict[parameter_attr.name]) > un_expect_lenth:
un_expect_lenth = len(parameter_un_expect_dict[parameter_attr.name])
for key in parameter_expect_dict.keys():
if key not in json.dumps(request_demo):
continue
value = copy.copy(parameter_expect_dict)
value[key] = ['NULL']
if parameters.get(key).is_need == 1:
status = "FAIL"
else:
status = "PASS"
if type == "1102":
name = "缺少参数%s" % key
else:
name = "缺少参数%s的值" % key
parameter_expect_list.append([value, status, name])
return parameter_expect_list
def _distinguish_parameters_by_is_needed(self, parameters):
"""
将必填参数和非必须参数分开成2个列表
"""
need_parameters = list()
no_need_parameters = list()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
if parameter_attr.is_need != 0:
need_parameters.append(parameter_attr)
else:
no_need_parameters.append(parameter_attr)
return need_parameters, no_need_parameters
def _replace_null_value_no_need_parameters(self, demo, no_need_parameter_attrs):
"""
将所有非必填参数置空
"""
really_body = copy.deepcopy(demo)
for parameter_attr in no_need_parameter_attrs:
if re.search(r"\"%s\"" % parameter_attr.name, demo):
check_para = r"\"%s\":\s[\[\{]" % parameter_attr.name
if re.search(check_para, demo):
continue
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
replace1 = "\"%s\": \"NULL\"" % parameter_attr.name
really_body = re.sub(patten1, replace1, really_body)
return really_body
def generate_case_body_all_1101_array(self, request_demo, parameters):
"""
生成1101用例对应的所有请求参数组合
"""
body_array = list()
need, no_need = self._distinguish_parameters_by_is_needed(parameters)
demo_json = json.dumps(request_demo, ensure_ascii=False)
if no_need:
no_need_demo_json = self._replace_null_value_no_need_parameters(demo_json, no_need)
body_array.append([json.loads(no_need_demo_json), "PASS", "所有非必填参数为空"])
for item in need:
parameter_attr = copy.copy(item)
demo_json_copy = copy.copy(demo_json)
if re.search("\"%s\"" % parameter_attr.name, demo_json_copy):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json_copy):
continue
name = "缺少参数%s" % parameter_attr.name
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
replace1 = "\"%s\": []" % parameter_attr.name
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = "\"%s\": \"NULL\"" % parameter_attr.name
really_body = re.sub(patten1, replace1, demo_json_copy)
if parameter_attr.is_need == 1:
status = "FAIL"
else:
status = "PASS"
body_array.append([json.loads(really_body), status, name])
return body_array
def _clean_all_none_json_char(self, really_body):
"""
清除1102场景删除缺少参数后不规则的字符串内容
"""
really_body = re.sub(",\s,", ",", really_body)
really_body = re.sub("\[\s?,", "[", really_body)
really_body = re.sub("\{\s?,", "{", really_body)
really_body = re.sub(",\s\]", "]", really_body)
really_body = re.sub(",\s\}", "}", really_body)
really_body = re.sub("\s,", "", really_body)
return really_body
def _replace_null_all_no_need_parameters(self, demo, no_need_parameter_attrs):
"""
将所有非必填参数置空
"""
really_body = copy.deepcopy(demo)
for parameter_attr in no_need_parameter_attrs:
if re.search(r"\"%s\"" % parameter_attr.name, demo):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo):
continue
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
replace1 = ""
really_body = re.sub(patten1, replace1, really_body)
really_body = self._clean_all_none_json_char(really_body)
return really_body
def generate_case_body_all_1102_array(self, request_demo, parameters):
"""
生成1102用例对应的所有参数组合
"""
body_array = list()
need, no_need = self._distinguish_parameters_by_is_needed(parameters)
demo_json = json.dumps(request_demo, ensure_ascii=False)
if no_need:
no_need_demo_json = self._replace_null_all_no_need_parameters(demo_json, no_need)
body_array.append([json.loads(no_need_demo_json), "PASS", "所有非必填参数均缺失"])
for item in need:
parameter_attr = copy.copy(item)
demo_json_copy = copy.copy(demo_json)
if re.search(r"\"%s\"" % parameter_attr.name, demo_json_copy):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json_copy):
continue
name = "缺少参数%s" % parameter_attr.name
expect_value = parameter_attr.normal_values.split(",")[0]
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = ""
really_body = re.sub(patten1, replace1, demo_json_copy)
really_body = self._clean_all_none_json_char(really_body)
if parameter_attr.is_need == 1:
status = "FAIL"
else:
status = "PASS"
body_array.append([json.loads(really_body), status, name])
return body_array
def generate_case_body_all_1201_array(self, request_demo, parameters):
"""
生成1201用例对应的所有参数组合
"""
body_array = list()
demo_json = json.dumps(request_demo, ensure_ascii=False)
for item in parameters:
parameter_attr = SetAttr(item)
if re.search(r"\"%s\"" % parameter_attr.name, demo_json):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json):
continue
for un_expect_item in self.un_expect_list.get(parameter_attr.name):
name = "参数%s使用异常值%s" % (parameter_attr.name, un_expect_item)
demo_json_copy = copy.copy(demo_json)
expect_value = parameter_attr.normal_values.split(",")[0]
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
replace1 = '"%s": ["%s"]' % (parameter_attr.name, un_expect_item)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = '"%s": "%s"' % (parameter_attr.name, un_expect_item)
really_body = re.sub(patten1, replace1, demo_json_copy)
status = "FAIL"
body_array.append([json.loads(really_body), status, name])
return body_array
def create_case_content(self, result_path, interface_info, parameters, demo, case_type, tags=None):
"""
生成用例的所有内容
"""
self._check_interface_confirm(interface_info)
body_content = list()
doc = self.generate_case_doc(interface_info, tags=tags, demo=demo)
case_file, _ = self.generate_case_file_path(interface_info)
interface_name = interface_info.name
if not parameters:
body = self.generate_case_body_content(interface_info.in_url, doc, interface_name, case_type,
interface_info.interface_describe, None, status="PASS")
else:
try:
body_array = self.generate_case_body_all_1001_array(demo, parameters)
self.normal_1001 = body_array[0]
except TypeError:
log.warning(traceback.print_exc())
body_array = []
self.normal_1001 = {}
if case_type == "1001":
try:
body = self.generate_case_body_content(interface_info.in_url, doc, interface_name, case_type,
interface_info.interface_describe, body_array, status="PASS")
except Exception as err:
msg = "生成用例%s,%s失败原因如下:%s" % (interface_info.type, interface_info.in_url, traceback.print_exc())
log.error(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
elif case_type == "1101" or case_type == "1102" or case_type == "1201":
body_temp_list = []
# parameters_temp = self._generate_parameter_to_dict(parameters)
try:
body_array = eval(
"self.generate_case_body_all_%s_array(self.normal_1001, parameters)" % case_type)
if case_type == "1101":
name = "缺少参数值"
elif case_type == "1102":
name = "缺少参数key和值"
elif case_type == "1201":
name = "参数使用异常值"
else:
name = ""
body_tmp = self.generate_case_body_content(interface_info.in_url, doc, interface_name,
case_type,
name, body_array, status="PASS")
body_temp_list.append(body_tmp)
body = "".join(body_temp_list)
except Exception as err:
msg = "生成用例%s,%s失败原因如下:%s" % (interface_info.type, interface_info.in_url, traceback.print_exc())
log.error(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
raise err
body_content.append(body)
self.file.write_content_to_case_file(case_file, "\n".join(body_content))
msg = "接口url%s,%s,生成case%s成功!路径为:%s" % (interface_info.type, interface_info.in_url, case_type, case_file)
log.info(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
return msg
def generate_case_body_content(self, url, doc, interface_name, case_type, description, body_para, status="PASS"):
body_content = list()
case_name = self._generate_case_name(interface_name, case_type, description)
body_content.append(case_name)
body_content.append(doc)
# 如何需要自动生成1001类型用例的请求body数据请将304/305行放开并把306~310注释
request = self._generate_case_body_content(url, body_content=body_para, status=status)
body_content.append(request)
# if case_type != "1001":
# request = self._generate_case_body_content(url, body_content=body_para, status=status)
# body_content.append(request)
# else:
# body_content.append(" #请自行添加用例请求数据!\n")
return "\n".join(body_content)
def _generate_case_body_content(self, url, body_content=None, status="PASS"):
body_list = []
if body_content is None:
try:
body_request = " ${resp} %s" % (self.file.all_url.get(url)[1])
except Exception as err:
log.error(traceback.print_exc())
body_request = " #url: %s 无法找到对应的关键字,请检查并更新!" % url
body_list.append(body_request)
body_list.append(" Log ${resp}")
if self.team.upper() in ["TMO"]:
body_validate_success = " check_rf ${resp} %s" % '请自行添加检查结果'
else:
body_validate_code = " Should Be Equal ${resp['code']} ${200}"
body_validate_success = " Should Be True ${resp['success']}"
body_list.append(body_validate_code)
body_list.append(body_validate_success)
else:
if not body_content:
body_create = " #request_demo无内容或者parameters在数据表中不全请先检查再生成 \n ${body} Evaluate %s" % body_content
body_list.append(body_create)
count = 0
for real_body in body_content:
result = {"message": "", "code": 200, "success": True}
temp = copy.copy(real_body)
if len(real_body) == 3 and isinstance(real_body, list):
if real_body[1] == "PASS" or real_body[1] == "FAIL":
real_body = temp[0]
status = temp[1]
case_description = temp[2]
body_list.append(" #%s" % case_description)
try:
module = load_module_from_file(self.file.kw.kw_file_path, self.team)
importlib.reload(module)
if isinstance(real_body, list):
temp_type = "*"
else:
temp_type = "**"
result = eval(
"%s.%s().%s(%sreal_body)" % (
"module", self.file.kw_class_name, self.file.all_url.get(url)[1], temp_type))
except:
msg = "#url: %s 无法通过参数得到请求结果!" % url
log.warning(real_body)
log.warning(msg)
log.warning(traceback.print_exc())
if "${time}" in str(real_body):
body_list.append(" ${time} get time epoch")
body_create = " ${body_%s} Evaluate %s" % (count, real_body)
try:
if isinstance(real_body, list):
body_request = " ${resp_%s} %s %s" % (
count, self.file.all_url.get(url)[1], "@{body_%s}" % count)
elif isinstance(real_body, dict):
body_request = " ${resp_%s} %s %s" % (
count, self.file.all_url.get(url)[1], "&{body_%s}" % count)
except Exception as err:
log.error(traceback.print_exc())
body_request = " #url: %s 无法找到对应的关键字,请检查并更新!" % url
body_list.append(body_create)
body_list.append(body_request)
body_list.append(" Log ${resp_%s}" % count)
if self.team.upper() in ["TMO"]:
body_validate_resp = " check_rf ${resp_%s} %s" % (count, result)
else:
body_validate_code = " Should Be Equal as strings ${resp_%s['code']} ${%s}"
# body_validate_success = " %s ${resp_%s['success']}"
body_validate_success = " %s ${resp_%s['success']} %s"
body_validate_resp = " %s ${resp_%s[\"message\"]} %s"
if isinstance(result, str):
body_validate_code = " #特殊接口返回!"
body_validate_success = " #返回为bytes类型的字符码。仅检查结果是否相等"
body_validate_resp = " Should Be Equal as strings ${resp_%s} %s" % (count, result)
elif status == "PASS":
body_validate_code = body_validate_code % (count, result['code'])
body_validate_success = body_validate_success % ("Should Be True", count, "")
try:
if result["message"]:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, result["message"])
else:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, '${EMPTY}')
except KeyError as err:
body_validate_resp = " #resp中没有message请后续自行添加"
log.warning(err)
log.warning("resp中没有message请后续自行添加")
else:
try:
body_validate_code = body_validate_code % (count, result['code'])
except Exception as e:
if "status" in result:
body_validate_code = body_validate_code % (count, result['status'])
body_validate_code = body_validate_code.replace("code", "status")
else:
raise KeyError("接口不中无code返回请与开发确认")
try:
body_validate_success = body_validate_success % (
"Should Be Equal As Strings", count, result["success"])
except Exception as err:
if "error" in result:
body_validate_success = body_validate_success % (
"Should Be Equal As Strings", count, result["error"])
body_validate_success = body_validate_success.replace("success", "error")
else:
raise KeyError("接口不中无success返回请与开发确认")
try:
if result.get("message"):
if re.search("[a-zA-Z0-9]{32}", result.get("message", "")):
body_validate_resp = body_validate_resp % ("should contain", count,
re.sub("[a-zA-Z0-9]{32}", "", result["message"]))
else:
body_validate_resp = body_validate_resp % (
"should be equal as strings", count, result["message"])
else:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, '${EMPTY}')
except KeyError as err:
body_validate_resp = " #resp中没有message请后续自行添加"
log.warning(err)
log.warning("resp中没有message请后续自行添加")
body_list.append(body_validate_code)
body_list.append(body_validate_success)
body_list.append(body_validate_resp)
# body_list.append(" #请自行添加data校验")
count += 1
body_list.append("\n")
return "\n".join(body_list)
def _generate_body_expect_type_value(self, type):
if type.lower() == 'integer':
value = random.randint(0, 100)
elif type.lower() == "string":
value = "".join([random.choice(string.digits + string.ascii_letters) for i in range(5)])
elif type.lower() == "date":
value = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
elif type.lower() == "boolean":
value = True
elif type.lower() == "array":
value = [1]
else:
value = "123"
return value
def _generate_body_un_expect_type_value(self, type):
value = ""
if type.lower() == 'integer':
value = "".join([random.choice(string.digits + string.ascii_letters) for i in range(5)])
elif type.lower() == "string":
value = random.randint(0, 10000)
else:
value = False
return value
def _analysis_dict_to_body_value(self, dict_none, dict_data):
"""
生成所有请求参数的组合列表
"""
data_list = []
if isinstance(dict_none, list):
if dict_none:
for index in range(len(dict_none)):
data = self._analysis_dict_to_body_value(dict_none[index], dict_data[index])
data_list.extend(data)
else:
data_list = dict_data
elif isinstance(dict_none, dict):
try:
len_num = self._get_nums(dict_data)
except Exception as e:
# log.error(e)
log.error("数据表request_parameters中参数不全请使用interface_hunter补全参数")
raise e
for i in range(0, len_num):
copy_dict = copy.copy(dict_none)
for key, value in copy_dict.items():
data = self._analysis_dict_to_body_value(copy_dict[key], dict_data[key])
if not data:
copy_dict[key] = None
elif i >= len(data):
copy_dict[key] = data[0]
if isinstance(value, list):
copy_dict[key] = [data[0]]
else:
copy_dict[key] = data[i]
if isinstance(value, list):
copy_dict[key] = [data[i]]
data_list.append(copy_dict)
else:
data_list = dict_data
return data_list
def _get_nums(self, dict_data):
"""
获取所有参数对应值的最大长度
"""
nums_list = [0]
if isinstance(dict_data, list):
for item in dict_data:
if isinstance(item, dict):
data = self._get_nums(dict_data[0])
nums_list.append(data)
else:
nums_list.append(len(dict_data))
elif isinstance(dict_data, dict):
for value in dict_data.values():
data = self._get_nums(value)
nums_list.append(data)
elif dict_data == None:
nums_list.append(0)
else:
nums_list.append(len(dict_data))
return max(nums_list)
def _analysis_case_parameters_to_value(self, interface_demo, parameters):
"""
根据demo将每个参数的对应值的列表写回demo
"""
body = copy.copy(interface_demo)
if isinstance(body, list):
for index in range(len(body)):
item = body[index]
body[index] = self._analysis_case_parameters_to_value(item, parameters)
elif isinstance(body, dict):
for key, value in body.items():
if isinstance(value, dict) or isinstance(value, list):
if value:
body[key] = self._analysis_case_parameters_to_value(body.get(key), parameters)
else:
body[key] = parameters.get(key)
else:
body[key] = parameters.get(key)
return body
def _analysis_case_parameters_to_dict(self, parameters):
"""
根据所有参数生成正常值 和异常值的列表
"""
expect_lenth = 0
un_expect_lenth = 0
parameter_expect_dict = dict()
parameter_un_expect_dict = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
if parameter_attr.normal_values:
if parameter_attr.normal_values.startswith("["):
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
if parameter_attr.type == "array":
parameter_expect_dict[parameter_attr.name] = [[x] for x in parameter_attr.normal_values.split(",")]
else:
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
continue
if len(parameter_expect_dict[parameter_attr.name]) > expect_lenth:
expect_lenth = len(parameter_expect_dict[parameter_attr.name])
if parameter_attr.exception_values:
parameter_un_expect_dict[parameter_attr.name] = parameter_attr.exception_values.split(",")
else:
# raise Exception("接口参数%s无异常值请先确认接口参数。" % parameter_attr.name)
parameter_un_expect_dict[parameter_attr.name] = []
if len(parameter_un_expect_dict[parameter_attr.name]) > un_expect_lenth:
un_expect_lenth = len(parameter_un_expect_dict[parameter_attr.name])
return parameter_expect_dict, parameter_un_expect_dict, expect_lenth, un_expect_lenth
def generate_case_doc(self, interface_info, tags=None, demo=None):
if demo:
lenth, demo_json, paras_d = self.file.kw._clean_demo_to_less_key(demo)
doc_list = list()
doc_list.append(
" [Documentation] {} 接口路径:{}, {}".format(interface_info.interface_describe,
interface_info.type, interface_info.in_url))
# doc_list.append(self.doc_content_row_temp.format("输入参数名", "参数类型", "说明"))
# for parameters_info in parameters:
# sub_doc = self._generate_case_doc(parameters_info)
# doc_list.append(sub_doc)
if tags:
tag_list = tags.split(",")
tag = " [Tags]"
for tag_ in tag_list:
tag += " %s" % tag_
doc_list.append(tag)
if lenth > 1:
doc_list.append(
" # 请求参数说明 h:放header里的参数 u:放url路径节点中的参数 d:不放在url中在请求参数 p放在url问号后在key-value结构参数")
return "\n".join(doc_list)
def _generate_case_doc(self, request_parameter):
doc_content = list()
parameter_attr = SetAttr(request_parameter)
row = self.doc_content_row_temp.format(parameter_attr.name, parameter_attr.type, parameter_attr.note)
doc_content.append(row)
return "\n".join(doc_content)
def generate_case_file_path(self, interface_info):
path_cell_list = self._generate_case_file_path_info(interface_info.in_url)
path_cell_list.insert(0, self.file.case_dir)
return os.path.abspath(os.path.join(*path_cell_list[:-1]) + ".robot"), path_cell_list[-1]
def _generate_case_file_path_info(self, url):
head_list = []
para_list = []
host, url_last = re.search(r"(\S+\.cn|\S+\.com)(\S+)", url).groups()
path_first = re.search("//([\w+\-]+)\.", host).group(1)
head_list.append(path_first)
search = re.findall("[\w+\-_]+|[\{\w+\-_\}]+", url_last)
for item in search:
if "{" in item:
temp = item.replace("{", "").replace("}", "")
para_list.append(temp)
else:
head_list.append(item)
return head_list
def run_interface_case_generate(result_path, interface, demo_info, parameters, case_instance, tags=None, normal=None):
case_type_list = ["1001", "1101", "1102", "1201"]
not_exist_case_type = list()
exist_case_name = list()
case_path, _ = case_instance.generate_case_file_path(interface)
case_name_head = interface.name
if parameters:
for case_type in case_type_list:
case_name = "-".join([case_name_head, case_type])
if not case_instance.file.check_case_exist(case_path, case_name):
not_exist_case_type.append(case_type)
else:
exist_case_name.append(case_name)
else:
case_name = "-".join([case_name_head, "1001"])
if not case_instance.file.check_case_exist(case_path, case_name):
not_exist_case_type.append("1001")
else:
exist_case_name.append(case_name)
for item in exist_case_name:
msg = "接口url%s,%s,已存在用例%s,请确认!" % (interface.type, interface.in_url, item)
log.warning(msg)
case_instance.file.write_content_to_case_file(result_path, msg + "\n")
if not_exist_case_type:
if normal:
not_exist_case_type = ["1001"]
case_instance.file.generate_case_file(case_path)
try:
demo = json.loads(demo_info.request_demo)
except Exception as err:
log.error(traceback.print_exc())
log.warning("the demo of interface(%s) is empty,please check." % interface.id)
demo = dict()
try:
result = list()
result.append("")
result.append("-" * 60)
result.append("用例生成成功!")
for case_type in not_exist_case_type:
body = case_instance.create_case_content(result_path, interface, parameters, demo, case_type, tags=tags)
# result.append(body)
result.append(body.split("")[1])
log.info("\n".join(result))
except:
log.error("could not generate case for interface id: %s, url: %s" % (interface.id, interface.in_url))
log.error(traceback.print_exc())
else:
msg = "接口url%s,%s,已存在用例,请确认!" % (interface.type, interface.in_url)
log.warning(msg)
case_instance.file.write_content_to_case_file(result_path, msg + "\n")

View File

@@ -0,0 +1,3 @@
目录结构说明:
使用者:王刚
用途存放自动生成RF层用例的脚本

View File

@@ -0,0 +1,7 @@
# -*- coding:utf-8 -*-
"""
Author: linyupeng
Email: linyupeng@huohua.cn
Create Data: 2021/4/9 19:08
"""

View File

@@ -0,0 +1,266 @@
# -*- coding:utf-8 -*-
import argparse
import copy
import datetime
import json
import os
import sys
import time
from base_framework.platform_tools.Keywords_service.keyword_service import SetAttr, SwaggerDBInfo, \
KWFileOperation, log, KWOperation, run_keyword_generage, Project_Path
# from base_framework.platform_tools.Swagger_scanner.scanner import SwaggerOnlineDebug
from base_framework.platform_tools.Testcase_service.case_service import ITCaseContentOperation, \
run_interface_case_generate, load_module_from_file
from base_framework.base_config.current_pth import *
from base_framework.public_tools.read_config import ReadConfig
from base_framework.platform_tools.Swagger_scanner.scanner_add import GenerateBody
dir_name = os.path.dirname(__file__)
interface_path = os.path.join(dir_name, "Swagger_scanner/interface.txt")
token_header = ["accesstoken", "user-token"]
def get_all_interface_info_from_file(path):
with open(path, encoding="UTF-8") as f:
content = f.readlines()
return content[1:]
def write_interface_info_to_file(content, path=interface_path):
with open(path, mode="r", encoding="UTF-8") as f:
line = f.readline().strip("\n")
with open(path, mode="w", encoding="UTF-8") as f1:
f1.write(line + "\n" + content)
def generate_result_file():
file_name = "reslut_%s.txt" % time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime())
result_dir = os.path.join(dir_name, "case_result")
result_file = os.path.abspath(os.path.join(result_dir, file_name))
if not os.path.exists(result_dir):
os.makedirs(result_dir)
with open(result_file, "w+", encoding="UTF-8") as f:
f.write("开始自动生成keyword和case! 开始时间:%s\n" % time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime()))
return result_file
def write_case_result_to_case_file(file_path, content):
with open(file_path, "a+", encoding="UTF-8") as f:
lines = f.readlines()
text = content + "\n"
if lines:
if lines[-1] != "\n":
text = "\n" + "\n" + content + "\n"
f.write(text)
def clean_the_interface_info(result_file, interface_info):
interface = tuple(interface_info.strip("\n").split(","))
interface_infos = SwaggerDBInfo.get_interface_info_by_url_and_type(interface[1], interface[0], interface[2])
if len(interface_infos) > 1:
msg = "接口url%s,%s,存在多条数据,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
elif len(interface_infos) == 0:
msg = "接口url%s,%s,在数据库中不存在,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
else:
interface_info_attr = SetAttr(interface_infos[0])
if interface_info_attr.is_used == 0:
msg = "接口url%s,%s,%s,已不在使用,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
elif interface_info_attr.at_numbers > 0:
msg = "接口url%s,%s,%s,已存在用例,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
else:
msg = "接口url%s,%s,%s,开始生成接口关键字和用例!" % interface
log.info(msg)
write_case_result_to_case_file(result_file, msg)
return interface_info_attr
return msg
def generate_swagger_url_and_option(swagger_info):
swagger_info_attr = SetAttr(swagger_info)
url = swagger_info_attr.sw_url
temp_list = url.split("v2")
swagger_url = temp_list[0] + 'swagger-ui.html'
option = '/v2' + temp_list[1]
return swagger_url, option, temp_list[0]
def get_demo_by_interface_info(interface_id):
body_demo = GenerateBody(interface_id)
request_data = body_demo.combine_request_parameters()
if request_data["d"]:
request_demo = body_demo.combine_request_data(request_data["d"][0], request_data["d"][1], key=0)
request_data["d"] = request_demo
return request_data
def clear_request_parameters(parameters):
"""
清除参数中作为token的参数
"""
parameters_result = copy.deepcopy(parameters)
for item in parameters:
if item.get("name").lower() in token_header:
parameters_result.remove(item)
return parameters_result
def clear_header_token_demo(demo_info):
"""
清除请求头中带有token这种无用参数
"""
demo_copy = json.loads(copy.deepcopy(demo_info.request_demo))
header = copy.deepcopy(demo_copy.get('h'))
for key, _ in demo_copy.get('h').items():
if key in token_header:
header.pop(key)
if not header:
demo_copy['h'] = {}
else:
demo_copy['h'] = header
demo_info.request_demo = json.dumps(demo_copy)
return demo_info
def generate_parameter_to_dict(parameters):
"""
将数据库查询到的参数以参数名组成字典
"""
dict_temp = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
dict_temp[parameter_attr.name] = parameter_attr
return dict_temp
def clean_un_use_parameter(demo, parameters):
"""
删除不再使用的参数
"""
if isinstance(demo, SetAttr):
demo_item = json.loads(demo.request_demo)
demo_copy = copy.deepcopy(demo_item)
else:
demo_item = copy.deepcopy(demo)
demo_copy = copy.deepcopy(demo)
parameters_dict = generate_parameter_to_dict(parameters)
if isinstance(demo, list):
demo_copy.clear()
for index in range(len(demo_item)):
demo_copy.append(clean_un_use_parameter(demo_item[index], parameters))
elif isinstance(demo_item, dict):
for key, values in demo_item.items():
if parameters_dict.get(key, None) and int(parameters_dict.get(key).is_need) == 2:
demo_copy.pop(key)
continue
demo_copy[key] = clean_un_use_parameter(values, parameters)
else:
demo_copy = demo_copy
return demo_copy
def confirm_demo_value_string(kw, case, demo_string, parameters):
"""
确认demo中的请求参数是不是纯值并非json说格式
"""
demo = copy.deepcopy(demo_string.request_demo)
demo_value = json.loads(demo)
if (not demo_value["d"]) and (demo_value["d"] != 0):
return demo_value
if (isinstance(demo_value["d"], str) or isinstance(demo_value["d"], int)):
demo_value["d"] = {parameters[0].get("name"): demo_value["d"]}
kw.kw_string = True
case.case_string = True
elif isinstance(demo_value["d"], list):
if not isinstance(demo_value["d"][0], dict):
demo_value["d"] = {parameters[0].get("name"): demo_value["d"]}
kw.kw_string = True
case.case_string = True
elif 'empty' in demo_value['d']:
kw.kw_array = demo_value['d'].pop('empty')
return demo_value
def confirm_correct_team_case(team):
interface_file_path = os.path.abspath(os.path.join(Project_Path,
"{team}".format(team=team), "library",
"{team}_interface.py".format(team=team.upper())))
try:
module = load_module_from_file(interface_file_path, team.upper())
real_team = team.upper()
except Exception as err:
real_team = team.lower()
return real_team
def run_interface_case(team, tags=None, platform=None, server=None, driver=None, normal=None):
team = confirm_correct_team_case(team)
kw_instance = KWFileOperation(team)
result_file = generate_result_file()
interface_contents = get_all_interface_info_from_file(interface_path)
keyword = KWOperation(team, kw_instance, server)
case = ITCaseContentOperation(team, kw_instance)
for interface_temp in interface_contents:
if interface_temp == "\n" or interface_temp.startswith("#"):
continue
interface = clean_the_interface_info(result_file, interface_temp)
if not isinstance(interface, str):
parameters = keyword.get_interface_parameters(interface.id)
parameters = clear_request_parameters(parameters)
if parameters:
demo = get_demo_by_interface_info(interface.id)
else:
demo = {"d": {}, "h": {}, "p": {}, "u": {}}
demo = [{"request_demo": json.dumps(demo)}]
demo_info = SetAttr(demo[0])
demo_info = clear_header_token_demo(demo_info)
demo_info.request_demo = json.dumps(clean_un_use_parameter(demo_info, parameters))
demo_info.request_demo = json.dumps(confirm_demo_value_string(keyword, case, demo_info, parameters))
kw_status = run_keyword_generage(result_file, interface, demo_info, parameters, keyword)
if kw_status:
msg = "接口url%s,%s,生成keyword成功接下来继续生成用例!" % (interface.type, interface.in_url)
log.info(msg)
write_case_result_to_case_file(result_file, msg)
run_interface_case_generate(result_file, interface, demo_info, parameters, case, tags, normal)
return True
else:
return interface
def print_usage():
parser_inst.print_usage()
parser_inst.exit(1, "生成用例失败,请根据使用帮助传入正确参数。")
def write_platform_to_running_env(platform, team):
if not platform:
platform = ""
cfg_opt = ReadConfig(env_choose_path)
cfg_opt.set_section("run_jira_id", "huohua-podenv", platform)
cfg_opt.set_section("run_evn_name", "current_team", team)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="请输入组名!")
parser.add_argument("-team", dest='team', help="TO or TMO")
parser.add_argument("-tag", dest='tags', help="标签", default=None)
parser.add_argument("-p", dest='platform', help="独立环境", default=None)
parser.add_argument("-s", dest='server', help="服务名", default=None)
parser.add_argument("-d", dest='driver', help="驱动路径", default=None)
parser.add_argument("-n", dest='normal', help="是否只生成正常用例", default=None)
args = parser.parse_args()
global parser_inst
parser_inst = parser
if not args.team:
print_usage()
else:
write_platform_to_running_env(args.platform, args.team)
run_interface_case(args.team, args.tags, args.platform, args.server, args.driver, args.normal)

View File

@@ -0,0 +1,264 @@
# -*- coding:utf-8 -*-
import argparse
import copy
import datetime
import json
import os
import sys
import time
from base_framework.platform_tools.Keywords_service.keyword_service1 import SetAttr, SwaggerDBInfo, \
KWFileOperation, log, KWOperation, run_keyword_generage, Project_Path
# from base_framework.platform_tools.Swagger_scanner.scanner import SwaggerOnlineDebug
from base_framework.platform_tools.Testcase_service.case_service1 import ITCaseContentOperation, \
run_interface_case_generate, load_module_from_file
from base_framework.base_config.current_pth import *
from base_framework.public_tools.read_config import ReadConfig
from base_framework.platform_tools.Swagger_scanner.scanner_add import GenerateBody
dir_name = os.path.dirname(__file__)
interface_path = os.path.join(dir_name, "Swagger_scanner/interface.txt")
token_header = ["accesstoken", "user-token"]
def get_all_interface_info_from_file(path):
with open(path, encoding="UTF-8") as f:
content = f.readlines()
return content[1:]
def write_interface_info_to_file(content, path=interface_path):
with open(path, mode="r", encoding="UTF-8") as f:
line = f.readline().strip("\n")
with open(path, mode="w", encoding="UTF-8") as f1:
f1.write(line + "\n" + content)
def generate_result_file():
file_name = "reslut_%s.txt" % time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime())
result_dir = os.path.join(dir_name, "case_result")
result_file = os.path.abspath(os.path.join(result_dir, file_name))
if not os.path.exists(result_dir):
os.makedirs(result_dir)
with open(result_file, "w+", encoding="UTF-8") as f:
f.write("开始自动生成keyword和case! 开始时间:%s\n" % time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime()))
return result_file
def write_case_result_to_case_file(file_path, content):
with open(file_path, "a+", encoding="UTF-8") as f:
lines = f.readlines()
text = content + "\n"
if lines:
if lines[-1] != "\n":
text = "\n" + "\n" + content + "\n"
f.write(text)
def clean_the_interface_info(result_file, interface_info, team):
interface = tuple(interface_info.strip("\n").split(","))
interface_infos = SwaggerDBInfo.get_interface_info_by_url_and_type(interface[1], interface[0], team)
if len(interface_infos) > 1:
msg = "接口url%s,%s,存在多条数据,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
elif len(interface_infos) == 0:
msg = "接口url%s,%s,在数据库中不存在,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
else:
interface_info_attr = SetAttr(interface_infos[0])
if interface_info_attr.is_used == 0:
msg = "接口url%s,%s,已不在使用,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
elif interface_info_attr.at_numbers > 0:
msg = "接口url%s,%s,已存在用例,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
else:
msg = "接口url%s,%s,开始生成接口关键字和用例!" % interface
log.info(msg)
write_case_result_to_case_file(result_file, msg)
return interface_info_attr
return msg
def generate_swagger_url_and_option(swagger_info):
swagger_info_attr = SetAttr(swagger_info)
url = swagger_info_attr.sw_url
temp_list = url.split("v2")
swagger_url = temp_list[0] + 'swagger-ui.html'
option = '/v2' + temp_list[1]
return swagger_url, option, temp_list[0]
def get_demo_by_interface_info(interface_id):
body_demo = GenerateBody(interface_id)
request_data = body_demo.combine_request_parameters()
if request_data["d"]:
request_demo = body_demo.combine_request_data(request_data["d"][0], request_data["d"][1], key=0)
request_data["d"] = request_demo
return request_data
def clear_request_parameters(parameters):
"""
清除参数中作为token的参数
"""
parameters_result = copy.deepcopy(parameters)
for item in parameters:
if item.get("name").lower() in token_header:
parameters_result.remove(item)
return parameters_result
def clear_header_token_demo(demo_info):
"""
清除请求头中带有token这种无用参数
"""
demo_copy = json.loads(copy.deepcopy(demo_info.request_demo))
header = copy.deepcopy(demo_copy.get('h'))
for key, _ in demo_copy.get('h').items():
if key in token_header:
header.pop(key)
if not header:
demo_copy['h'] = {}
else:
demo_copy['h'] = header
demo_info.request_demo = json.dumps(demo_copy)
return demo_info
def generate_parameter_to_dict(parameters):
"""
将数据库查询到的参数以参数名组成字典
"""
dict_temp = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
dict_temp[parameter_attr.name] = parameter_attr
return dict_temp
def clean_un_use_parameter(demo, parameters):
"""
删除不再使用的参数
"""
if isinstance(demo, SetAttr):
demo_item = json.loads(demo.request_demo)
demo_copy = copy.deepcopy(demo_item)
else:
demo_item = copy.deepcopy(demo)
demo_copy = copy.deepcopy(demo)
parameters_dict = generate_parameter_to_dict(parameters)
if isinstance(demo, list):
demo_copy.clear()
for index in range(len(demo_item)):
demo_copy.append(clean_un_use_parameter(demo_item[index], parameters))
elif isinstance(demo_item, dict):
for key, values in demo_item.items():
if parameters_dict.get(key, None) and int(parameters_dict.get(key).is_need) == 2:
demo_copy.pop(key)
continue
demo_copy[key] = clean_un_use_parameter(values, parameters)
else:
demo_copy = demo_copy
return demo_copy
def confirm_demo_value_string(kw, case, demo_string, parameters):
"""
确认demo中的请求参数是不是纯值并非json说格式
"""
demo = copy.deepcopy(demo_string.request_demo)
demo_value = json.loads(demo)
if (not demo_value["d"]) and (demo_value["d"] != 0):
return demo_value
if (isinstance(demo_value["d"], str) or isinstance(demo_value["d"], int)):
demo_value["d"] = {parameters[0].get("name"): demo_value["d"]}
kw.kw_string = True
case.case_string = True
elif isinstance(demo_value["d"], list):
if not isinstance(demo_value["d"][0], dict):
demo_value["d"] = {parameters[0].get("name"): demo_value["d"]}
kw.kw_string = True
case.case_string = True
return demo_value
def confirm_correct_team_case(team):
interface_file_path = os.path.abspath(os.path.join(Project_Path,
"{team}".format(team=team), "library",
"{team}_interface.py".format(team=team.upper())))
try:
module = load_module_from_file(interface_file_path, team.upper())
real_team = team.upper()
except Exception as err:
real_team = team.lower()
return real_team
def run_interface_case(team, tags=None, server=None, normal=None):
team = confirm_correct_team_case(team)
kw_instance = KWFileOperation(team, server)
result_file = generate_result_file()
interface_contents = get_all_interface_info_from_file(interface_path)
keyword = KWOperation(team, kw_instance)
case = ITCaseContentOperation(team, kw_instance)
for interface_temp in interface_contents:
if interface_temp == "\n" or interface_temp.startswith("#"):
continue
interface = clean_the_interface_info(result_file, interface_temp, team)
if not isinstance(interface, str):
parameters = keyword.get_interface_parameters(interface.id)
parameters = clear_request_parameters(parameters)
if parameters:
demo = get_demo_by_interface_info(interface.id)
else:
demo = {"d": {}, "h": {}, "p": {}, "u": {}}
demo = [{"request_demo": json.dumps(demo)}]
demo_info = SetAttr(demo[0])
demo_info = clear_header_token_demo(demo_info)
demo_info.request_demo = json.dumps(clean_un_use_parameter(demo_info, parameters))
demo_info.request_demo = json.dumps(confirm_demo_value_string(keyword, case, demo_info, parameters))
kw_status = run_keyword_generage(result_file, interface, demo_info, parameters, keyword)
if kw_status:
msg = "接口url%s,%s,生成keyword成功接下来继续生成用例!" % (interface.type, interface.in_url)
log.info(msg)
write_case_result_to_case_file(result_file, msg)
run_interface_case_generate(result_file, interface, demo_info, parameters, case, tags, normal)
return True
else:
return interface
def print_usage():
parser_inst.print_usage()
parser_inst.exit(1, "生成用例失败,请根据使用帮助传入正确参数。")
def write_platform_to_running_env(platform, team):
if not platform:
platform = ""
cfg_opt = ReadConfig(env_choose_path)
cfg_opt.set_section("run_jira_id", "huohua-podenv", platform)
cfg_opt.set_section("run_evn_name", "current_team", team)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="请输入组名!")
parser.add_argument("-team", dest='team', help="TO or TMO")
parser.add_argument("-tag", dest='tags', help="标签", default=None)
parser.add_argument("-p", dest='platform', help="独立环境", default=None)
parser.add_argument("-s", dest='server', help="服务名", default=None)
parser.add_argument("-d", dest='driver', help="驱动路径", default=None)
parser.add_argument("-n", dest='normal', help="是否只生成正常用例", default=None)
args = parser.parse_args()
global parser_inst
parser_inst = parser
if not args.team:
print_usage()
else:
write_platform_to_running_env(args.platform, args.team)
run_interface_case(args.team, args.tags, args.platform, args.server, args.driver, args.normal)

View File

@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
__author__ = 'huaxuemin'
import argparse
from runner9 import CaseRunner
usage_info = '''
usage:
$ case_runner.py -w %WORKSPACE% -c %case_path% -t tc1,tc2 -i p0,p1,p2 -e norun,del -r True
run tag cases in workspace
'''
def usage():
print(usage_info)
exit(-1)
def get_test_case_name(build_id):
try:
if build_id:
import pymysql
db = pymysql.connect(host="mysql.qa.huohua.cn", user="qa-dev", password="jaeg3SCQt0", database="sparkatp",
charset='utf8')
cursor = db.cursor()
get_sql = "SELECT test_case_id FROM sparkatp.build_params WHERE build_info_id={}".format(build_id)
cursor.execute(get_sql)
res = cursor.fetchone()
test_case_name = ""
if res:
test_case_name = res[0].replace("(", "*").replace(")", "*").replace(" ", "*")
cursor.close()
db.close()
return test_case_name
except Exception as e:
print(e)
return ""
def main():
parser = argparse.ArgumentParser(description="This is for introduction parameter")
parser.add_argument("-w", dest='workspace', help="case_runner workspace")
parser.add_argument("-c", dest='case_path', help="case path")
parser.add_argument("-t", dest='test_case', default='', help="test case")
parser.add_argument("-i", dest='include', default='', help="include tag")
parser.add_argument("-e", dest='exclude', default='', help="exclude tag")
parser.add_argument("-r", dest='rerun', default='true', help="rerun failed case")
parser.add_argument("-env", dest='special_env', default='', help="special env")
parser.add_argument("-team", dest='team', default='', help="job name with team ")
parser.add_argument("-penv", dest='physics_env', help="QA or SIM", default='QA')
parser.add_argument("-b", dest='business', help="hh or hhi", default='hh')
parser.add_argument("-b_id", dest='build_info_id', help="build_info_id", default='')
parser.add_argument("-b_url", dest='build_url', help="build_url", default='')
parser.add_argument("-is_db", dest='is_use_db', help="is_use_db", default='0')
args = parser.parse_args()
if args.workspace is None:
usage()
workspace = args.workspace
case_path = args.case_path
test_case = args.test_case
include = args.include
exclude = args.exclude
rerun = args.rerun
special_env = args.special_env
team = args.team.split("-")[0]
physics_env = args.physics_env
business = args.business
build_info_id = args.build_info_id
build_url = args.build_url
is_use_db = args.is_use_db
if str(is_use_db) == "1" and build_info_id:
test_case_name = get_test_case_name(build_info_id)
test_case = test_case_name if test_case_name else test_case
# if team not in ["CC", "EN", "H2R", "LaLive", "SCM", "TMO", "TO", "ASOP", "ASTWB", "ASORG"]:
# team = ""
case_runner = CaseRunner(workspace, case_path, test_case, include, exclude, rerun, special_env, team, physics_env,
business)
try:
# 更新独立环境代号到配置文件
case_runner.update_platform()
# 检查工作目录名字对应的端口,有就使用,没有就分配
case_runner.assign_port()
# 查找对应端口进程并杀掉
case_runner.check_port()
# 更新KWL和RF中的端口
case_runner.update_port()
# 更新重名测试套
case_runner.update_suite()
# 启动KWL
case_runner.start_kwl()
# 构建用例
case_runner.run_cases()
# 构建完成后停止KWL
case_runner.check_port()
except Exception as e:
print(e)
# 构建完成后入库build_url
case_runner.record_build_url(build_info_id, build_url)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,683 @@
# -*- coding: utf-8 -*-
__author__ = 'huaxuemin'
from logbook import Logger
import shutil
import errno
import os
import sqlite3
from pathlib import Path
import random
import socket
import configparser
import codecs
import time
import json
import re
import requests
logging = Logger(__name__)
HERE = os.path.dirname(os.path.abspath(__file__))
ROBOT_LOG_LEVEL = 'INFO'
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class OSType:
WIN, LINUX, UNKNOWN = range(3)
def __init__(self):
pass
@staticmethod
def get_type():
import platform
system_name = platform.system()
if system_name.lower() == 'windows':
return OSType.WIN
elif system_name.lower() == 'linux':
return OSType.LINUX
else:
return OSType.UNKNOWN
def run_process(cmd_str, out_p=False):
"""
run command
cmd_str unicode string.
"""
if OSType.WIN == OSType.get_type():
# cmd_str = cmd_str.encode('gbk')
cmd_str = cmd_str
elif OSType.LINUX == OSType.get_type():
cmd_str = cmd_str.encode('utf-8')
else:
raise RuntimeError("your os is not support.")
logging.info('cmd: %s' % cmd_str)
print(cmd_str)
import subprocess
from subprocess import PIPE
close_fds = False if OSType.WIN == OSType.get_type() else True
if out_p:
p = subprocess.Popen(cmd_str, shell=True, close_fds=close_fds, stdout=PIPE)
p.wait()
return p.returncode, p.stdout.read()
else:
p = subprocess.Popen(cmd_str, shell=True, close_fds=close_fds, stdout=subprocess.DEVNULL)
p.wait()
return p.returncode, None
class CaseRunner:
def __init__(self, workspace, case_path, test_case, include, exclude, rerun="true", special_env="", team="",
physics_env="QA", business="hh"):
self.workspace = workspace
self.case_path = case_path
self.test_case = test_case
self.include = include
self.exclude = exclude
self.rerun = rerun
self.special_env = special_env
self.team = team
self.physics_env = physics_env
self.business = business
self.con = sqlite3.connect(HERE + "/case_runner.db")
self.cur = self.con.cursor()
self.def_port = set("9" + "".join(map(str, random.choices(range(10), k=3))) for i in range(500))
self.env_port = None
self.pid = None
self.wait_time = 120
self.all_dir_name = set()
self.galaxy_server_name_to_swagger = {"peppa-teach-opt-cms-api": "teach-opt-cms-api",
"peppa-teach-biz-server": "peppa-teach-biz",
"peppa-market-server": "peppa-market",
"peppa-teach-parker-server": "peppa-teach-parker",
"peppa-course-server": "peppa-course"}
self.swagger_name_to_galaxy = {"teach-opt-cms-api": "peppa-teach-opt-cms-api",
"peppa-teach-biz": "peppa-teach-biz-server",
"peppa-market": "peppa-market-server",
"peppa-teach-parker": "peppa-teach-parker-server",
"peppa-course": "peppa-course-server"}
# 获取opengalaxy ssotoken相关变量
self.ops_uri = "http://opengalaxy.bg.huohua.cn"
self.showUsername = "luohong"
self.username = "luohong"
self.password = "Lh123456789@"
self.sso_login_url = "https://sso.huohua.cn/authentication/form"
self.redirect_url = "https://sso.huohua.cn/oauth/authorize?client_id=open-galaxy&response_type=code&redirect_uri=http://opengalaxy.bg.huohua.cn/api/v1/users/user/ssologin/"
def getSsoToken(self):
session = requests.session()
post_data = dict()
post_data['showUsername'] = self.showUsername
post_data['username'] = self.username
post_data['password'] = self.password
session.post(url=self.sso_login_url, data=post_data, allow_redirects=True, verify=False)
resp = session.get(
url=self.redirect_url,
allow_redirects=False,
verify=False)
resp1 = session.get(
url=resp.headers['Location'],
allow_redirects=False,
verify=False)
ssoToken = resp1.headers["Set-Cookie"].split(";")[4].split("=")[2]
return ssoToken
def update_platform(self):
if len(self.special_env) > 0:
config_file = str(Path(self.workspace) / "base_framework/base_config/env_choose.ini")
fd = open(config_file, encoding='utf-8')
data = fd.read()
if data[:3] == codecs.BOM_UTF8:
data = data[3:]
files = codecs.open(config_file, "w")
files.write(data)
files.close()
fd.close()
cf = configparser.ConfigParser(allow_no_value=True)
cf.read(config_file, encoding='utf-8')
cf.set("run_jira_id", "huohua-podenv", self.special_env)
with open(config_file, 'w') as fw: # 循环写入
cf.write(fw)
def assign_port(self):
workspace_base_name = Path(self.workspace).name
query_port = "SELECT PORT FROM ENV_PORT WHERE NAME='{}'".format(workspace_base_name)
res_port = self.cur.execute(query_port).fetchall()
query_all_port = "SELECT PORT FROM ENV_PORT"
res_all_port = self.cur.execute(query_all_port).fetchall()
not_use_port = (self.def_port - set(str(item[0]) for item in res_all_port)).pop()
print("not use port is {}".format(not_use_port))
self.env_port = not_use_port
if len(res_port) == 0:
insert_name_port_sql = "INSERT INTO ENV_PORT(NAME,PORT)VALUES(?,?)"
self.cur.execute(insert_name_port_sql, (workspace_base_name, not_use_port)).fetchall()
self.con.commit()
else:
update_name_port_sql = "UPDATE ENV_PORT SET PORT='{}' WHERE NAME='{}'".format(self.env_port,
workspace_base_name)
self.cur.execute(update_name_port_sql).fetchall()
self.con.commit()
self.cur.close()
self.con.close()
print("use port is {}".format(self.env_port))
# def assign_port(self):
# workspace_base_name = Path(self.workspace).name
# query_port = "SELECT PORT FROM ENV_PORT WHERE NAME='{}'".format(workspace_base_name)
# res = self.cur.execute(query_port).fetchall()
# if len(res) == 0:
# query_all_port = "SELECT PORT FROM ENV_PORT"
# res_all_port = self.cur.execute(query_all_port).fetchall()
# not_use_port = (self.def_port - set(str(item[0]) for item in res_all_port)).pop()
# print("not use port is {}".format(not_use_port))
# insert_name_port_sql = "INSERT INTO ENV_PORT(NAME,PORT)VALUES(?,?)"
# self.cur.execute(insert_name_port_sql, (workspace_base_name, not_use_port)).fetchall()
# self.con.commit()
# self.env_port = not_use_port
# else:
# self.env_port = str(res[0][0])
# self.cur.close()
# self.con.close()
# print("use port is {}".format(self.env_port))
def _get_not_used_port(self):
find_pid_linux_cmd = "lsof -i:{}".format(self.env_port)
res_code, res_context = run_process(find_pid_linux_cmd, out_p=True)
if len(res_context) > 0:
return True
else:
return False
def check_port(self):
if OSType.WIN == OSType.get_type():
find_pid_win_cmd = 'netstat -ano | findstr {} | findstr LISTENING'.format(self.env_port)
res_code, res_context = run_process(find_pid_win_cmd, out_p=True)
if res_code == 0:
print(res_context)
if len(res_context) > 0:
try:
self.pid = str(res_context).split()[-1].replace("\\r\\n'", "")
self._kill_pid()
except IndexError:
pass
elif OSType.LINUX == OSType.get_type():
find_pid_linux_cmd = "lsof -i:{}".format(self.env_port)
res_code, res_context = run_process(find_pid_linux_cmd, out_p=True)
if res_code == 0:
print(res_context)
# 获取pid
if len(res_context) > 0:
try:
self.pid = str(res_context).split("\\n")[1].split()[1]
self._kill_3_pid()
except IndexError:
pass
else:
raise RuntimeError("your os is not support.")
def _kill_3_pid(self):
self._kill_pid()
count = 3
while count > 0:
find_pid_linux_cmd = "lsof -i:{}".format(self.env_port)
res_code, res_context = run_process(find_pid_linux_cmd, out_p=True)
if len(res_context) > 0:
time.sleep(2)
self.pid = str(res_context).split("\\n")[1].split()[1]
self._kill_pid()
count -= 1
continue
else:
break
def _kill_pid(self):
if OSType.WIN == OSType.get_type():
kill_pid_cmd = "taskkill /f /pid {}".format(self.pid)
elif OSType.LINUX == OSType.get_type():
kill_pid_cmd = "kill -9 {}".format(self.pid)
else:
raise RuntimeError("your os is not support.")
res_code, res_context = run_process(kill_pid_cmd)
if res_code:
raise RuntimeError("kill pid: {} failed. error: {}".format(self.pid, res_context))
def update_port(self):
main_py = Path(self.workspace) / "base_framework/main.py"
tmp_py = Path(self.workspace) / "base_framework/tmp.py"
shutil.copy(main_py, tmp_py)
src_context = "port=9999"
dst_context = "port={}".format(self.env_port)
self._replace_file_context(tmp_py, main_py, src_context, dst_context)
env_robot = Path(self.workspace) / "{}/test_case/Resource/AdapterKws/env.robot".format(self.team)
tmp_robot = Path(self.workspace) / "{}/test_case/Resource/AdapterKws/tmp.robot".format(self.team)
shutil.copy(env_robot, tmp_robot)
src_context = "127.0.0.1:9999"
dst_context = "127.0.0.1:{}".format(self.env_port)
self._replace_file_context(tmp_robot, env_robot, src_context, dst_context)
def update_suite(self):
case_dir = Path(self.workspace) / "{}/test_case".format(self.team)
self._set_path(case_dir)
case_src_path = Path(self.case_path)
if case_src_path.is_file():
base_name = case_src_path.name.split(case_src_path.suffix)[0]
if base_name.upper() in self.all_dir_name:
self.case_path = str(case_src_path.with_name(base_name + "9" + case_src_path.suffix))
self._replace_path(case_dir)
def _set_path(self, src_path):
for p in src_path.iterdir():
if p.is_dir():
self.all_dir_name.add(p.name.upper())
self._set_path(p)
def _replace_path(self, src_path):
for p in src_path.iterdir():
if p.is_dir():
self._replace_path(p)
elif p.is_file():
base_name = p.name.split(p.suffix)[0]
if base_name.upper() in self.all_dir_name:
p.rename(p.with_name(base_name + "9" + p.suffix))
def start_kwl(self):
startup_path = Path(self.workspace) / "base_framework"
startup = "startup.py"
exec_cmd_params = ""
if len(self.special_env) > 0:
exec_cmd_params = exec_cmd_params + "-j {} ".format(self.special_env)
if len(self.team) > 0:
exec_cmd_params = exec_cmd_params + "-t {} ".format(self.team)
if len(self.business) > 0:
exec_cmd_params = exec_cmd_params + "-b {} ".format(self.business)
if OSType.WIN == OSType.get_type():
exec_cmd_path = "cd {} && python {} ".format(startup_path, startup)
exec_cmd = exec_cmd_path + exec_cmd_params
elif OSType.LINUX == OSType.get_type():
exec_cmd_path = "cd {} && python3 {} ".format(startup_path, startup)
exec_cmd = exec_cmd_path + exec_cmd_params
else:
raise RuntimeError("your os is not support.")
exec_cmd = exec_cmd[:-1]
# import subprocess
# from subprocess import PIPE
# close_fds = False if OSType.WIN == OSType.get_type() else True
# subprocess.Popen(exec_cmd, shell=True, close_fds=close_fds, stdout=subprocess.DEVNULL)
try:
pro = self._start_kwl_server(exec_cmd)
if not pro.is_alive():
print("--------pro.exec_status: ", pro.is_alive())
pro.kill()
time.sleep(5)
self._kill_3_pid()
self._start_kwl_server(exec_cmd)
except Exception as e:
print("--------error: ", e)
@staticmethod
def _start_kwl_server(exec_cmd):
from multiprocessing import Process
p = Process(target=run_process, args=(exec_cmd,))
p.daemon = True
p.start()
time.sleep(30)
return p
@staticmethod
def _replace_file_context(src_file, dst_file, src_context, dst_context):
with open(src_file, "r") as f_src:
with open(dst_file, "w") as f_dst:
for line in f_src:
if src_context in line:
f_dst.writelines(line.replace(src_context, dst_context))
else:
f_dst.writelines(line)
def _get_report_dir(self):
"""
Create %WORKSPACE%/Report 用于存放对应构建的构建日志
"""
report_dir = os.path.join(self.workspace, 'Report')
if not os.path.exists(report_dir):
logging.info(u'create report directory: %s' % report_dir)
os.makedirs(report_dir)
return report_dir
def _get_report_ci_out_dir(self):
"""
Create %WORKSPACE%/Report/ci_out用于存放构建日志
"""
ci_out_dir = os.path.join(self.workspace, 'Report', 'ci_out')
if os.path.exists(ci_out_dir):
logging.info(u'create ci output directory: %s' % ci_out_dir)
shutil.rmtree(ci_out_dir)
return ci_out_dir
@staticmethod
def copy_any_thing(src, dst):
try:
shutil.copytree(src, dst)
except OSError as exc:
if exc.errno == errno.ENOTDIR:
shutil.copy(src, dst)
else:
raise
def _wait_kwl_run(self):
i = 0
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
while i < self.wait_time:
try:
s.connect(("127.0.0.1", int(self.env_port)))
s.shutdown(2)
s.close()
return
except socket.error:
i += 1
s.close()
def run_cases(self):
"""
run cases by workspace include exclude.
"""
self._wait_kwl_run()
report_dir = os.path.join(self.workspace, 'Report')
if os.path.exists(report_dir):
logging.info(u'clean report directory: %s' % report_dir)
shutil.rmtree(report_dir)
os.makedirs(report_dir)
if self.rerun == 'true' or self.rerun == '':
self._rerun_failed_cases()
else:
self._default_run_cases()
out_dir = os.path.join(self._get_report_dir(), 'out')
if os.path.exists(out_dir):
# copy report to ci_out.
self.copy_any_thing(self._get_report_dir(), self._get_report_ci_out_dir())
else:
raise RuntimeError(u'%s directory is not exist.' % out_dir)
def _default_run_cases(self):
"""
exec robot and output log to {report_dir}/out directory.
return command execute exit code.
"""
test_case_cmdstr = []
if len(self.test_case) > 0:
for tc in self.test_case.split(','):
test_case_cmdstr.append(u'--test %s' % tc)
includes_cmdstr = []
if len(self.include) > 0:
for include in self.include.split(','):
includes_cmdstr.append(u'--include %s' % include)
excludes_cmdstr = []
if len(self.exclude) > 0:
for exclude in self.exclude.split(','):
excludes_cmdstr.append(u'--exclude %s' % exclude)
output_directory = os.path.join(self._get_report_dir(), 'out')
Path(output_directory).mkdir(exist_ok=True)
# Set ROBOT_SYSLOG_FILE and ROBOT_SYSLOG_LEVEL environment variable.
os.environ['ROBOT_SYSLOG_FILE'] = os.path.join(self._get_report_dir(), 'robot_syslog.txt')
os.environ['ROBOT_SYSLOG_LEVEL'] = ROBOT_LOG_LEVEL
logging.info(u'building cases...')
res_code, _ = run_process(' '.join(
[u'robot'] + test_case_cmdstr + excludes_cmdstr + includes_cmdstr + [u'-d', output_directory,
self.case_path]))
# clear ROBOT_SYSLOG_FILE(NONE) and ROBOT_SYSLOG_LEVEL environment variable.
os.environ['ROBOT_SYSLOG_FILE'] = 'NONE'
return res_code
def _rerun_failed_cases(self):
"""
execute robot twice(the second execute is only failed in first.)
and remerge output to {report_dir}/out directory.
"""
logging.info('rerunfailed cases mode...')
return_code = self._default_run_cases()
logging.info('first run exit code: %s' % return_code)
if not return_code:
logging.info('first cases built successfully')
return
logging.info('the first cases to build there is failure cases')
output_directory = os.path.join(self._get_report_dir(), u'out')
if not os.path.exists(output_directory):
raise RuntimeError(u'the first cases to built throw exception.')
output_directory_r1 = os.path.join(self._get_report_dir(), u'first_out')
output_directory_r2 = os.path.join(self._get_report_dir(), u'second_out')
logging.info('rename the first cases to build the output directory')
cur_dir = os.getcwd()
os.chdir(self._get_report_dir())
if OSType.WIN == OSType.get_type():
cmd_str = 'ren out first_out'
elif OSType.LINUX == OSType.get_type():
cmd_str = 'cp -R out first_out'
else:
raise RuntimeError("your os is not support.")
os.system(cmd_str)
os.chdir(cur_dir)
output_directory_cmdstr = [u'-d', output_directory_r2]
rerun_failed_cmdstr = [u'--rerunfailed', os.path.join(output_directory_r1, u'output.xml')]
logging.info('rerunfailed test cases...')
run_process(' '.join([u'robot'] + output_directory_cmdstr + rerun_failed_cmdstr + [self.case_path]))
if not os.path.exists(output_directory_r2):
raise RuntimeError(u'the second cases to built throw throw exception.')
# Set ROBOT_SYSLOG_FILE and ROBOT_SYSLOG_LEVEL environment variable.
os.environ['ROBOT_SYSLOG_FILE'] = os.path.join(self._get_report_dir(), 'robot_syslog2.txt')
os.environ['ROBOT_SYSLOG_LEVEL'] = ROBOT_LOG_LEVEL
logging.info('merge report...')
run_process(' '.join([u'rebot', u'-d', output_directory, u'-o', u'output.xml', u'--merge',
os.path.join(output_directory_r1, u'output.xml'),
os.path.join(output_directory_r2, u'output.xml')]))
# clear ROBOT_SYSLOG_FILE(NONE) and ROBOT_SYSLOG_LEVEL environment variable.
os.environ['ROBOT_SYSLOG_FILE'] = 'NONE'
def record_build_url(self, build_id, report_url):
try:
if build_id:
import pymysql
db = pymysql.connect(host="mysql.qa.huohua.cn", user="qa-dev", password="jaeg3SCQt0",
database="sparkatp", charset='utf8')
cursor = db.cursor()
if build_id and report_url:
update_sql = "UPDATE sparkatp.build_info set report_url='{}',status=2 WHERE id={}".format(
report_url, build_id)
cursor.execute(update_sql)
cursor.fetchall()
try:
self.get_jacoco_report(cursor, build_id)
except Exception as e:
print(e)
db.commit()
cursor.close()
db.close()
except Exception as e:
print(e)
def get_tester_by_project_id(self):
"""
获取project_id和tester
:return:
"""
try:
import pymysql
db = pymysql.connect(host="10.250.200.53", user="root", password="peppa@test", database="tools",
charset='utf8')
cursor = db.cursor()
db.commit()
cursor.close()
db.close()
except Exception as e:
print(e)
def get_project_id_by_build_id(self, cursor, build_id):
try:
if build_id:
get_scene_id_sql = "SELECT scene_id FROM build_info where id='{}'".format(
build_id)
cursor.execute(get_scene_id_sql)
scene_id_info = cursor.fetchone()
if scene_id_info:
scene_id = scene_id_info[0]
get_project_id_sql = "SELECT project_id FROM scene_new where id='{}'".format(
scene_id)
cursor.execute(get_project_id_sql)
project_id_info = cursor.fetchone()
if project_id_info:
project_id = project_id_info[0]
return project_id
return 0
return 0
except Exception as e:
print(e)
def get_jacoco_report(self, cursor, build_id):
get_server_name_sql = "SELECT run_server_list FROM build_info where id='{}' and is_jacoco=1".format(build_id)
cursor.execute(get_server_name_sql)
server_name_info = cursor.fetchone()
if server_name_info:
server_name_list = eval(server_name_info[0])
for server_name in server_name_list:
if server_name == "PEPPA-TEACH-API" or server_name == "peppa-teach-api" or "-EXECUTOR" in server_name.upper():
continue
self._do_jacoco_report(server_name, build_id, cursor)
def _do_jacoco_report(self, project_name, build_id, cursor):
if not self.special_env:
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, self.special_env, "master",
self.special_env, "1", "", "QA环境构建不需要收集增量覆盖率")
cursor.execute(insert_data)
return
elif self.special_env.upper() == "NONE" or self.special_env.upper() == "QA":
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, self.special_env, "master",
self.special_env, "1", "", "QA环境构建不需要收集增量覆盖率")
cursor.execute(insert_data)
return
# 收集覆盖率报告
import requests
import json
if self.team.upper() in ["CC", "LALIVE", "H2R"]:
self.team = "CC"
if self.team.upper() in ["SCM", "ES"]:
self.team = "ES"
if self.team.upper() in ["TO", "TMO"]:
self.team = "TTS"
current_version = self.get_branch_from_open_galaxy(self.special_env, project_name)
if current_version == "master":
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, current_version, current_version,
self.special_env, "1", "", "master不需要收集增量覆盖率")
cursor.execute(insert_data)
return
if not current_version:
print("-----------------: {}, 独立环境: {} 服务名: {}".format("未在独立环境中服务,不收集覆盖率", self.special_env, project_name))
return
if project_name.lower() in self.galaxy_server_name_to_swagger.keys():
project_name = self.galaxy_server_name_to_swagger[project_name.lower()]
base_version = "master"
url = "http://10.250.0.252:8989/cov/syncCollectionCov"
params = {"baseVersion": base_version, "businessName": self.team, "currentVersion": current_version,
"departmentName": "质量保障中心",
"envName": self.special_env, "isBranch": 1, "isDiff": 2, "projectName": project_name}
res = requests.post(url, json=params)
if res.status_code == 200:
if json.loads(res.text)["msg"] != "success":
logging.error("{}收集覆盖率报告失败err: {}".format(project_name, res.text))
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, current_version, base_version,
self.special_env, "3", "", res.text)
cursor.execute(insert_data)
else:
jacoco_report_id = json.loads(res.text)["data"]
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, int(jacoco_report_id), self.team, project_name, current_version, base_version,
self.special_env, "0", "", "")
cursor.execute(insert_data)
else:
logging.error("{}收集覆盖率报告失败status{}err: {}".format(project_name, str(res.status_code), res.text))
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, current_version, base_version,
self.special_env, "3", "", res.text)
cursor.execute(insert_data)
# print(res.text)
def get_branch_from_open_galaxy(self, special_env, server_name):
if server_name.lower() in self.swagger_name_to_galaxy.keys():
server_name = self.swagger_name_to_galaxy[server_name.lower()]
try:
headers = {
"Api-Token": "2a1cbc25e0d183e1ec9fe5872c1433617c96da9e2905c4371d8f979479910aa1"}
open_galaxy_url = "http://opengalaxy.bg.huohua.cn/api/v1/hcloud/tree/node/sec/application?sec_name={}".format(
special_env)
res = requests.get(open_galaxy_url, headers=headers)
if res.status_code == 200:
result = json.loads(res.text)
for item in result["data"]["results"]:
if item["name"].lower() == server_name.lower():
branch = item["branch"]
return branch
return ""
else:
print("获取运维服务列表失败status: {}, res: {}".format(res.status_code, res.text))
return ""
except Exception as e:
print(e)
return ""
if __name__ == '__main__':
# workspace = "/root/workspaces/huaxuemin-dev"
workspace = r"E:\huohua\auto\huaxuemin-dev"
# case_path = "/root/workspaces/huaxuemin-dev/HuoHuaTestCase/EN/1.接口/Peppa-Eng-Live/play_back.robot"
case_path = r"E:\huohua\auto\huaxuemin-dev\HuoHuaTestCase\EN\1.接口\Peppa-Eng-Live\play_back.robot"
test_case = ""
include = ""
exclude = ""
test = CaseRunner(workspace, case_path, test_case, include, exclude)
# test.assign_port()
# test.check_port()
# test.update_port()
# test.start_kwl()
# test.run_cases()
# test.check_port()
# build_info_id = '16329'
# build_url = 'http://10.250.200.1:8080/jenkins/view/QE_JOB/job/qe_job1/63/'
# test.record_build_url(build_info_id, build_url)
res = test.get_branch_from_open_galaxy("HHC-92692", "peppa-asset-server")
print(res)

View File

@@ -0,0 +1,194 @@
# -*- coding:utf-8 -*-
import json
import requests
import logging
from base_framework.public_tools.utils import Tools
obj_tool = Tools()
class FeiShuMultidimensionalTableOperations:
def __init__(self, app_token, table_id, view_id):
"""
初始化飞书多维表格操作类
Args:
app_token: 多维表格token登录飞书多维表格按F12右侧接口列表中找带token的接口复制token
table_id: 多维表格id登录飞书多维表格在浏览器地址栏中找到table=后面的值
view_id: 多维表格视图id登录飞书多维表格在浏览器地址栏中找到view=后面的值
Note
如果返回显示禁止访问的话,说明你的飞书表格没有给应用开通权限,需要在更多-添加应用中添加此应用,并开通权限
这里我们使用的应用名是QualityAssurance-Customer-CD-QA
如果你搜多不到这个应用,说明你没有此应用的使用权限,找陈江或刚哥开通权限
"""
self.app_token = app_token # 表格token
self.table_id = table_id # 表格id
self.view_id = view_id # 表格视图id
self.app_id = 'cli_a53b456397bdd00c' # 飞书应用的app_id,使用前,多维表格需要在更多-添加应用中添加此应用,并开通权限
self.app_secret = 's3U4tXp9lTz7imMDQsHDNcoO4AgzXWk7' # 飞书应用的app_secret
self.fs_login_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
self.http_session = requests.session()
self.tenant_access_token = ( # 以应用身份请求api的鉴权凭证每次获取后有效期为2小时
self.__get_tenant_access_token_by_feishu().get("tenant_access_token"))
self.http_session.headers.update({"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.tenant_access_token}",
"User-Agent": "lark-api-explorer/v1"})
def __get_tenant_access_token_by_feishu(self):
"""功能获取飞书应用的tenant_access_token以应用身份请求api的鉴权凭证每次获取后有效期为2小时"""
headers = {'Content-Type': 'application/json; charset=utf-8'}
data = {'app_id': self.app_id, 'app_secret': self.app_secret}
r = requests.post(self.fs_login_url, data=json.dumps(data), headers=headers, verify=False)
r_json = r.json()
if r_json.get('code') == 0:
return {"expire": r_json.get('expire'), "tenant_access_token": r_json.get('tenant_access_token')}
else:
raise Exception(f"获取原始tenant_access_token失败{r_json}")
def fsmt_search_from_m_table(self, filter_dict=None, sort=None, automatic_fields=False, field_names=None):
"""
功能从多维表格中查询数据详细说明见https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search
Args:
filter_dict: 过滤条件类型list格式[{"conjunction": "string", "conditions": "list"]
conjunction: 过滤条件之间的关系取值范围and、or当仅有一个条件时也需要填写此字段
conditions: 过滤条件,格式:[{"field_name": "string", "operator": "string", "value": "list"}]
field_name: 筛选条件的左值值为字段的名称类型string
operator: 筛选条件的运算符,
取值范围is等于
isNot不等于
contains包含
doesNotContain不包含
isEmpty为空
isNotEmpty不为空
isGreater大于
isGreaterEqual大于等于
isLess小于
isLessEqual小于等于
likeLIKE 运算符。暂未支持
inIN 运算符。暂未支持
value: 筛选条件的右值值为字段的值类型list
若查询条件是时间格式,且为具体时间或日期时,格式为:["ExactDate", "13位的时间戳"]
同时也支持时间段查询,格式为:["具体的时间段描述字符"],如:
["Today"]:今天
["Tomorrow"]: 明天
["Yesterday"]:昨天
["CurrentWeek"]:本周
["LastWeek"]:上周
["CurrentMonth"]:本月
["LastMonth"]:上月
["TheLastWeek"]:过去七天内
["TheNextWeek"]:未来七天内
["TheLastMonth"]:过去三十天内
["TheNextMonth"]:未来三十天内
sort: 排序条件类型list格式[{"field_name": "string", "desc": "bool"}]
field_name: 排序字段的名称类型string
desc: 是否倒序排序类型bool, 默认值False
automatic_fields: 控制是否返回自动计算的字段, true 表示返回
field_names: 指定本次查询返回记录中包含哪些字段
Returns: {"code": 0, "msg": "",
"data": {"items": "入参中自定义的返回字段"
"has_more": "bool,是否还有更多项",
"page_token": "boolean, 分页标记,当 has_more 为 true 时,会同时返回新的 page_token否则不返回 page_token",
"total": "int, 总数"}}
"""
url = ("https://open.feishu.cn/open-apis/bitable/v1/apps/{}/tables/{}/records/search"
.format(self.app_token, self.table_id))
request_data = {"view_id": self.view_id,
"filter": filter_dict,
"automatic_fields": automatic_fields,
"sort": sort,
"field_names": field_names}
try:
rs = self.http_session.post(url, data=json.dumps(request_data), verify=False)
except Exception as e:
raise Exception(f"调用飞书接口{url}失败:{e}")
rj_json = rs.json()
if rj_json.get('code') == 0:
return rj_json
else:
raise Exception(f"调用飞书接口{url}失败:\n{rs.status_code}, {rs.text}")
def fsmt_insert_data_to_m_table(self, m_table_column_dict):
"""
https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_create
api文档地址https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/batch_create
"""
url = ("https://open.feishu.cn/open-apis/bitable/v1/apps/{}/tables/{}/records/batch_create"
.format(self.app_token, self.table_id))
request_data = {"records": []}
for column_dict in m_table_column_dict:
request_data["records"].append({"fields": column_dict})
try:
rs = self.http_session.post(url, data=json.dumps(request_data), verify=False)
except Exception as e:
raise Exception(f"调用飞书接口{url}失败:{e}")
rj_json = rs.json()
if rj_json.get('code') == 0:
return rj_json
else:
raise Exception(f"调用飞书接口{url}失败:\n{rs.status_code}, {rs.text}")
def fsmt_update_one_data_to_m_table(self, record_id, m_table_column_dict):
"""
https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/:record_id/update
api文档https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/update
"""
url = f"""https://open.feishu.cn/open-apis/bitable/v1/apps/{self.app_token}/tables/{self.table_id}/records/{record_id}"""
data = {"fields": m_table_column_dict}
logging.info(f"更新数据请求参数为:{data}")
rs = self.http_session.put(url, data=json.dumps(data), verify=False)
rj_json = rs.json()
if rj_json.get('code') == 0:
return rj_json
else:
logging.info(f"更新异常,异常返回结果为:{rs}")
def batch_update_data(self, table_id, update_records_data_list):
"""
https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_update
api文档https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/batch_update
"""
url = f"""https://open.feishu.cn/open-apis/bitable/v1/apps/{self.app_token}/tables/{table_id}/records/batch_update"""
data = {"records": update_records_data_list}
logging.info(f"批量更新数据请求参数为:{data}")
rs = self.http_session.post(url, data=json.dumps(data), verify=False)
rj_json = rs.json()
if rj_json.get('code') == 0:
return rj_json
else:
logging.info(f"批量更新异常,异常返回结果为:{rs}")
raise Exception(f"调用飞书接口{url}失败:{rs}")
def delete_data(self):
pass
if __name__ == '__main__':
fs = FeiShuMultidimensionalTableOperations(app_token="HssibyRf4anbpxsA3G2c1IPwnic",
table_id="tblVbNoZE1RI3Hdo",
view_id="vewx9lK9hI")
filter_dict = {"conjunction": "and",
"conditions": [{"field_name": "时间", "operator": "is", "value": ["Today"]}]}
sort = [{"field_name": "时间", "desc": True}]
field_names = ["时间", "入班", "换班", "补课", "月份"]
rsp = fs.fsmt_search_from_m_table(filter_dict=filter_dict, sort=sort, field_names=field_names)
if rsp.get("code") == 0:
if int(rsp.get("data").get("total")) == 0:
ci_data = [['2024-07-24', 7022, 2040, 105]]
request_data = []
for item in ci_data:
ci_date = obj_tool.get_format_date(r_time="{} 00:00:00".format(item[0]), r_type=13)
request_data.append({"时间": ci_date, "入班": item[1], "换班": item[2], "补课": item[3],
"月份": "{}".format(int(item[0].split("-")[1]))})
for item in request_data:
print(item)
# request_data = [{"时间": 1721664000000, "入班": 123, "换班": 124, "补课": 125, "月份": "7月"},
# {"时间": 1721750400000, "入班": 223, "换班": 224, "补课": 225, "月份": "7月"}]
rsp = fs.fsmt_insert_data_to_m_table(m_table_column_dict=request_data)
print(rsp)
else:
print("数据已存在,本次不写入...")
else:
print("查询失败:{}".format(rsp))

View File

@@ -0,0 +1,204 @@
# -*- coding:utf-8 -*-
"""
功能通过kibana查询logstash日志
进度:待完善....
"""
import requests
import json
from datetime import datetime, timedelta, timezone
class LogstashLogKibana:
def __init__(self, k_user="wuyonggang", k_pwd="Mima@123"):
self.kibana_host = "https://logstashlog-kibana.qc.huohua.cn/internal/bsearch"
self.kibana_user = k_user
self.kibana_pwd = k_pwd
self.r_header = {'kbn-version': '7.14.2',
'Content-Type': 'application/json; charset=gbk',
'sec-ch-ua-mobile': r'?0'}
self.request = requests.session()
self._login_kibana()
def _login_kibana(self):
url = "https://logstashlog-kibana.qc.huohua.cn/internal/security/login"
payload = json.dumps({"providerType": "basic",
"providerName": "basic",
"currentURL": "https://logstashlog-kibana.qc.huohua.cn/login?msg=LOGGED_OUT",
"params": {"username": self.kibana_user, "password": self.kibana_pwd}
})
resp = self.request.post(url, headers=self.r_header, data=payload)
print(resp)
def _format_message(self, message):
"""
格式化消息
:param message: 消息
:return: 格式化后的消息
"""
def _get_time_tamp(self, minute=10):
# 获取当前时间
current_time = datetime.utcnow().replace(tzinfo=timezone.utc)
# 计算十分钟前的时间
ten_minutes_ago = current_time - timedelta(minutes=minute)
# 格式化时间为 Elasticsearch 时间戳格式
gte_timestamp = ten_minutes_ago.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
lte_timestamp = current_time.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
return gte_timestamp, lte_timestamp
def lk_query_kibana_log(self, minutes=15, app_name=None, querys=None):
if querys is None:
querys = ['java.lang.NullPointerException']
if not (isinstance(querys, list) or isinstance(querys, dict)):
raise ValueError('querys{},必须为列表或者字典'.format(querys))
gte_timestamp, lte_timestamp = self._get_time_tamp(minute=minutes)
query_con = []
if isinstance(querys, dict):
query_cons = {"bool": {"should": [{"match_phrase": querys}], "minimum_should_match": 1}}
else:
for query in querys:
if len(querys) == 1:
query_cons = {
"multi_match": {
"type": "phrase",
"query": query,
"lenient": True
}}
else:
query_con.append({
"multi_match": {
"type": "phrase",
"query": query,
"lenient": True
}
})
query_cons = {
"bool": {
"filter": query_con}}
url = "https://logstashlog-kibana.qc.huohua.cn/internal/bsearch"
payload = json.dumps({
"batch": [
{
"request": {
"params": {
"index": "logstash-qc-logstashlog*",
"body": {
"size": 10000,
"sort": [
{
"@timestamp": {
"order": "desc",
"unmapped_type": "boolean"
}
}
],
"version": True,
"fields": [
{
"field": "*",
"include_unmapped": "true"
},
{
"field": "@timestamp",
"format": "strict_date_optional_time"
},
{
"field": "end_data",
"format": "strict_date_optional_time"
},
{
"field": "end_date",
"format": "strict_date_optional_time"
},
{
"field": "start_date",
"format": "strict_date_optional_time"
}
],
"aggs": {
"2": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "30m",
"time_zone": "Asia/Shanghai",
"min_doc_count": 1
}
}
},
"script_fields": {},
"stored_fields": [
"*"
],
"runtime_mappings": {},
"_source": False,
"query": {
"bool": {
"must": [],
"filter": [
query_cons,
{
"range": {
"@timestamp": {
"gte": gte_timestamp,
"lte": lte_timestamp,
"format": "strict_date_optional_time"
}
}
},
{
"match_phrase": {
"APP_NAME": app_name
}
}
],
"should": [],
"must_not": []
}
},
"highlight": {
"pre_tags": [
"@kibana-highlighted-field@"
],
"post_tags": [
"@/kibana-highlighted-field@"
],
"fields": {
"*": {}
},
"fragment_size": 2147483647
}
},
"track_total_hits": True,
"preference": 1708481407352
}
},
"options": {
# "sessionId": "473ee7d3-be00-411e-a925-71e2f669a230",
"isRestore": False,
"strategy": "ese",
"isStored": False
}
}
]
})
print(payload)
response = self.request.post(url, headers=self.r_header, data=payload)
# binary_data = response.text
# 解码为字符串并解析为 JSON 对象
json_string = response.text
json_data = json.loads(json_string)
print(json_data)
# print(response.json())
# path = os.path.join(self.data_path_list, file_name)
# with open(path, 'w+', encoding='utf-8') as f:
# f.write(response.text)
if __name__ == '__main__':
lk = LogstashLogKibana()
lk.lk_query_kibana_log(minutes=15, app_name="peppa-sparkle-scheduler",
querys=['java.lang.NullPointerException'])

View File

@@ -0,0 +1,55 @@
import concurrent.futures
import requests
import time
import json
cost_time = []
ALL_TIMES = 10000
def make_request(url, method='GET', data=None):
try:
start_time = int(time.time() * 1000)
if method.upper() == 'POST':
response = requests.post(url, json=data, headers={"content-type": "application/json;charset=UTF-8"})
else: # 默认使用GET方法
response = requests.get(url, params=data)
end_time = int(time.time() * 1000)
elapsed_time = end_time - start_time
cost_time.append(elapsed_time)
return response.text, elapsed_time
except requests.RequestException as e:
return f"Request failed: {e}", None
def main():
r_url = {'url': 'https://swagger.qa.huohua.cn/peppa-teach-timetable-server/timetableStudentServiceApi/queryListByIds',
'method': 'POST', 'data': [225838679]}
requests_list = [r_url] * ALL_TIMES
m_start_time = int(time.time() * 1000)
success_times = 0
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
future_to_req = {executor.submit(make_request, req['url'], req['method'], req['data']): req for req in requests_list}
for future in concurrent.futures.as_completed(future_to_req):
req = future_to_req[future]
try:
data, elapsed_time = future.result()
if data and json.loads(data).get('code') == 200:
success_times += 1
# print(f"URL: {req['url']}\nMethod: {req['method']}\nData: {req['data']}\nResponse:\n{data}\nElapsed time: {elapsed_time:.2f} seconds\n")
print("-----:本次耗时{}ms".format(elapsed_time))
except Exception as e:
print(f"Error fetching {req['url']}: {e}")
m_end_time = int(time.time() * 1000)
all_times = (m_end_time - m_start_time)/1000
tps = ALL_TIMES / all_times
print("共计访问接口:{}次,成功访问接口:{}次,持续时长:{}tps: {}"
.format(len(cost_time), success_times, all_times, tps))
print("成功访问接口:{}".format(success_times))
print("单次最大耗时:{}ms".format(max(cost_time)))
print("单次最小耗时:{}ms".format(min(cost_time)))
print("单次平均耗时:{}ms".format(sum(cost_time) / len(cost_time)))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,403 @@
import requests
import hashlib
import json
from bs4 import BeautifulSoup
from datetime import datetime
import time
def send_to_feishu(webhook_url, content, keyword="bug"):
"""
发送消息到飞书机器人
Args:
webhook_url: 飞书机器人的webhook地址
content: 要发送的消息内容
keyword: 关键词,需要包含在消息中
"""
try:
# 构建消息体
message = {
"msg_type": "text",
"content": {
"text": f"{keyword}\n{content}"
}
}
# 发送请求
headers = {'Content-Type': 'application/json'}
response = requests.post(webhook_url,
data=json.dumps(message),
headers=headers,
timeout=10)
if response.status_code == 200:
print(f"✅ 消息发送成功到飞书")
return True
else:
print(f"❌ 飞书消息发送失败: {response.status_code}")
print(f"响应: {response.text}")
return False
except Exception as e:
print(f"❌ 发送飞书消息异常: {e}")
return False
def get_all_bugs_and_send():
"""获取所有产品的Bug并发送到飞书"""
base_url = "http://39.170.26.156:8888"
username = "qiaoxinjiu"
password = "Qiao123456"
# 飞书配置
feishu_webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/c7288ada-1c0c-472a-b652-a475a9586302"
keyword = "bug"
print("获取禅道Bug列表")
print("=" * 50)
# 登录
session = requests.Session()
password_md5 = hashlib.md5(password.encode()).hexdigest()
login_resp = session.post(f"{base_url}/api.php/v1/tokens",
json={"account": username, "password": password_md5},
headers={'Content-Type': 'application/json'})
if login_resp.status_code not in [200, 201]:
error_msg = f"❌ 禅道登录失败: {login_resp.status_code}"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
token = login_resp.json().get('token')
session.headers.update({'Token': token})
print("✅ 登录成功")
# 获取所有产品
products_resp = session.get(f"{base_url}/api.php/v1/products")
if products_resp.status_code != 200:
error_msg = f"❌ 获取产品列表失败: {products_resp.status_code}"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
products = products_resp.json().get('products', [])
print(f"📦 共 {len(products)} 个产品")
all_bugs = []
# 遍历每个产品获取Bug只获取产品ID为2的
for product in products:
product_id = product.get('id')
product_name = product.get('name')
# 只获取产品ID为2的
if product_id != 2:
continue
print(f"\n获取产品 '{product_name}' 的Bug...")
# 获取HTML页面
bugs_url = f"{base_url}/bug-browse-{product_id}.html"
bugs_resp = session.get(bugs_url, params={'product': product_id, 'limit': 100})
if bugs_resp.status_code == 200:
try:
html = bugs_resp.text
soup = BeautifulSoup(html, "html.parser")
# 找 bug 列表的 table
table = (
soup.find("table", id="bugList")
or soup.find("table", class_="table")
or soup.find("table")
)
if not table:
print(" ❌ 未找到 bug 列表 table")
continue
# 解析表头
header_tr = table.find("tr")
if not header_tr:
print(" ❌ 未找到表头行")
continue
header_map = {}
for idx, th in enumerate(header_tr.find_all(["th", "td"])):
text = th.get_text(strip=True)
if not text:
continue
if "ID" == text or text == "编号":
header_map["id"] = idx
elif "标题" in text:
header_map["title"] = idx
elif "状态" in text:
header_map["status"] = idx
elif "严重" in text:
header_map["severity"] = idx
elif "优先" in text or "优先级" in text:
header_map["pri"] = idx
elif "指派" in text:
header_map["assignedTo"] = idx
elif "创建" in text or "打开" in text:
header_map["openedDate"] = idx
bugs = []
# 遍历表体行
for tr in table.find_all("tr")[1:]:
tds = tr.find_all("td")
if not tds:
continue
# id
bug_id = tr.get("data-id")
if not bug_id and "id" in header_map and header_map["id"] < len(tds):
bug_id = tds[header_map["id"]].get_text(strip=True)
if not bug_id:
continue
# 标题
title = ""
if "title" in header_map and header_map["title"] < len(tds):
cell = tds[header_map["title"]]
link = cell.find("a")
title = (link or cell).get_text(strip=True)
# 状态
status = ""
if "status" in header_map and header_map["status"] < len(tds):
status = tds[header_map["status"]].get_text(strip=True)
# 严重程度
severity = ""
if "severity" in header_map and header_map["severity"] < len(tds):
sev_cell = tds[header_map["severity"]]
severity = sev_cell.get_text(strip=True)
if not severity:
sev_span = sev_cell.find("span")
if sev_span and sev_span.get("title"):
severity = sev_span.get("title").strip()
# 优先级
pri = ""
if "pri" in header_map and header_map["pri"] < len(tds):
pri = tds[header_map["pri"]].get_text(strip=True)
# 指派给
assigned_to = ""
if "assignedTo" in header_map and header_map["assignedTo"] < len(tds):
assigned_to = tds[header_map["assignedTo"]].get_text(strip=True)
# 创建/打开日期
opened_date = ""
if "openedDate" in header_map and header_map["openedDate"] < len(tds):
opened_date = tds[header_map["openedDate"]].get_text(strip=True)
bug = {
"id": bug_id,
"title": title,
"statusName": status,
"severity": severity,
"pri": pri,
"assignedToName": assigned_to,
"openedDate": opened_date,
"product_name": product_name,
}
bugs.append(bug)
print(f" ✅ 解析出 {len(bugs)} 个Bug")
all_bugs.extend(bugs)
except Exception as e:
print(f" ❌ 解析失败: {e}")
else:
print(f" ❌ 获取失败: {bugs_resp.status_code}")
# 处理并发送统计信息
if all_bugs:
# 计算统计数据
today_str = datetime.today().strftime("%Y-%m-%d")
today_md_str = datetime.today().strftime("%m-%d")
total_bugs = len(all_bugs)
# 今日新增Bug
today_bugs = [
b for b in all_bugs
if (
today_str in str(b.get("openedDate", "")).strip()
or today_md_str in str(b.get("openedDate", "")).strip()
)
]
# 未关闭Bug状态不包含"已关闭"、"已解决"等)
open_bugs = [
b for b in all_bugs
if not any(kw in str(b.get("statusName", "")).lower() for kw in ["closed", "resolved", "done", "cancel"])
and not any(kw in str(b.get("statusName", "")) for kw in ["已关闭", "已解决", "已完成", "已取消"])
]
# 按指派人员统计
assigned_stats = {}
for bug in all_bugs:
assigned = bug.get("assignedToName", "未指派")
assigned_stats[assigned] = assigned_stats.get(assigned, 0) + 1
# 按状态统计
status_stats = {}
for bug in all_bugs:
status = bug.get("statusName", "未知")
status_stats[status] = status_stats.get(status, 0) + 1
# 构建飞书消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
message = f"🐛 禅道Bug统计报告\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 产品: 智慧运营平台V1.0\n"
message += f"================================\n"
message += f"📈 Bug统计概览:\n"
message += f" • 总Bug数: {total_bugs}\n"
message += f" • 今日新增: {len(today_bugs)}\n"
message += f" • 未关闭Bug: {len(open_bugs)}\n"
message += f"\n📋 状态分布:\n"
# 添加状态统计
for status, count in sorted(status_stats.items()):
message += f"{status}: {count}\n"
message += f"\n👥 指派人员统计:\n"
# 添加指派统计前5名
sorted_assigned = sorted(assigned_stats.items(), key=lambda x: x[1], reverse=True)[:5]
for person, count in sorted_assigned:
message += f"{person}: {count}\n"
# 今日新增Bug详情
if today_bugs:
message += f"\n🆕 今日新增Bug详情 ({len(today_bugs)}个):\n"
today_bugs.sort(key=lambda x: int(x.get('id', 0)), reverse=True)
for bug in today_bugs[:10]: # 只显示前10个
bug_url = f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html"
message += f" [#{bug.get('id')}] {bug.get('title', '')[:30]}...\n"
message += f" 状态: {bug.get('statusName', '未知')} | 严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')}\n"
# 未关闭Bug详情前5个
if open_bugs:
open_bugs.sort(key=lambda x: int(x.get('id', 0)), reverse=True)
message += f"\n⚠️ 最新未关闭Bug (前5个):\n"
for bug in open_bugs[:5]:
bug_url = f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html"
message += f" [#{bug.get('id')}] {bug.get('title', '')[:30]}...\n"
message += f" 状态: {bug.get('statusName', '未知')} | 严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')}\n"
message += f"\n🔗 禅道地址: {base_url}"
# 打印到控制台
print(f"\n{'=' * 80}")
print(message)
print(f"{'=' * 80}")
# 发送到飞书
print("\n发送消息到飞书...")
success = send_to_feishu(feishu_webhook, message, keyword)
if success:
print(f"✅ Bug统计报告已发送到飞书")
else:
print(f"❌ 飞书消息发送失败")
else:
message = f"📭 禅道Bug统计报告\n"
message += f"⏰ 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
message += f"📊 产品: 智慧运营平台V1.0\n"
message += f"================================\n"
message += f"✅ 当前没有Bug数据\n"
message += f"🔗 禅道地址: {base_url}"
print(message)
send_to_feishu(feishu_webhook, message, keyword)
def send_simple_report():
"""发送简化的Bug统计报告到飞书"""
base_url = "http://39.170.26.156:8888"
username = "qiaoxinjiu"
password = "Qiao123456"
# 飞书配置
feishu_webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/c7288ada-1c0c-472a-b652-a475a9586302"
keyword = "bug"
print("获取禅道Bug统计")
print("=" * 50)
try:
# 登录
session = requests.Session()
password_md5 = hashlib.md5(password.encode()).hexdigest()
login_resp = session.post(f"{base_url}/api.php/v1/tokens",
json={"account": username, "password": password_md5},
headers={'Content-Type': 'application/json'})
if login_resp.status_code not in [200, 201]:
error_msg = f"❌ 禅道登录失败"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
token = login_resp.json().get('token')
session.headers.update({'Token': token})
print("✅ 登录成功")
# 获取产品ID=2的Bug页面
bug_url = f"{base_url}/bug-browse-2.html"
response = session.get(bug_url)
if response.status_code != 200:
error_msg = f"❌ 无法获取Bug页面"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
# 简单统计Bug数量
import re
bug_matches = re.findall(r'bug-view-(\d+)\.html', response.text)
total_bugs = len(set(bug_matches)) # 去重
# 构建简单消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
message = f"🐛 禅道Bug速报\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 产品: 智慧运营平台V1.0\n"
message += f"================================\n"
message += f"📈 当前总Bug数: {total_bugs}\n"
message += f"🔗 查看详情: {bug_url}\n"
message += f"\n💡 提示: 详细统计请查看禅道系统"
print(f"发现 {total_bugs} 个Bug")
# 发送到飞书
success = send_to_feishu(feishu_webhook, message, keyword)
if success:
print(f"✅ 简版Bug报告已发送到飞书")
else:
print(f"❌ 飞书消息发送失败")
except Exception as e:
error_msg = f"❌ 获取Bug统计异常: {str(e)}"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
if __name__ == "__main__":
# 运行完整版本(带详细统计)
get_all_bugs_and_send()
# 或者运行简化版本(只发速报)
# send_simple_report()

View File

@@ -0,0 +1,237 @@
from __future__ import annotations
"""
ZenDao bug list crawler.
Design overview:
1. Read credentials + crawl settings from zendao_config.ini.
2. Use ZenDao REST API (`api.php/v1/accessTokens`) to obtain a short-lived token.
3. Pull paginated bug records under the configured product via `/api.php/v1/products/{product_id}/bugs`.
4. Export normalized bug data to CSV (default) or JSON, usable by other automation.
"""
import argparse
import configparser
import csv
import hashlib
import json
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Iterable, List, Sequence
import requests
from requests import Response, Session
BUG_EXPORT_FIELDS: Sequence[str] = (
"id",
"title",
"status",
"severity",
"pri",
"openedBy",
"openedDate",
"assignedTo",
"resolvedBy",
"resolution",
"lastEditedDate",
)
DEFAULT_CONFIG_PATH = Path(__file__).with_name("zendao_config.ini")
class ZenDaoAPIError(RuntimeError):
"""Raised when ZenDao returns an unexpected payload."""
@dataclass
class ZenDaoSettings:
base_url: str
username: str
password: str
product_id: int
per_page: int = 50
max_pages: int = 10
verify_ssl: bool = False
timeout: int = 10
auth_mode: str = "password" # password | token
api_token: str | None = None
auth_endpoint: str = "api.php/v1/tokens"
token_header: str = "Token"
password_hash: str = "md5" # md5 | plain
@classmethod
def from_file(cls, path: Path, section: str = "zendao") -> "ZenDaoSettings":
parser = configparser.ConfigParser()
if not path.exists():
raise FileNotFoundError(f"Config file not found: {path}")
parser.read(path, encoding="utf-8")
if section not in parser:
raise KeyError(f"Section [{section}] not found in {path}")
cfg = parser[section]
return cls(
base_url=cfg.get("base_url", "").rstrip("/"),
username=cfg.get("username", ""),
password=cfg.get("password", ""),
product_id=cfg.getint("product_id", fallback=0),
per_page=cfg.getint("per_page", fallback=50),
max_pages=cfg.getint("max_pages", fallback=10),
verify_ssl=cfg.getboolean("verify_ssl", fallback=False),
timeout=cfg.getint("timeout", fallback=10),
auth_mode=cfg.get("auth_mode", "password").lower(),
api_token=cfg.get("api_token"),
auth_endpoint=cfg.get("auth_endpoint", "api.php/v1/tokens"),
token_header=cfg.get("token_header", "Token"),
password_hash=cfg.get("password_hash", "md5").lower(),
)
def validate(self) -> None:
missing = [
name
for name, value in (
("base_url", self.base_url),
("username", self.username),
("password", self.password),
)
if not value
]
if missing:
raise ValueError(f"Missing config values: {', '.join(missing)}")
if self.product_id <= 0:
raise ValueError("product_id must be > 0")
if self.auth_mode not in {"password", "token"}:
raise ValueError("auth_mode must be 'password' or 'token'")
if self.password_hash not in {"md5", "plain"}:
raise ValueError("password_hash must be 'md5' or 'plain'")
if self.auth_mode == "token" and not self.api_token:
raise ValueError("api_token required when auth_mode=token")
class ZenDaoClient:
def __init__(self, settings: ZenDaoSettings, session: Session | None = None) -> None:
self.settings = settings
self.session: Session = session or requests.Session()
self._token: str | None = None
def authenticate(self) -> str:
if self.settings.auth_mode == "token":
if not self.settings.api_token:
raise ValueError("api_token missing in config")
self.session.headers.update({self.settings.token_header: self.settings.api_token})
self._token = self.settings.api_token
return self._token
payload = {
"account": self.settings.username,
"password": self._format_password(self.settings.password),
}
response = self._request("post", self.settings.auth_endpoint, json=payload)
data = response.json()
token = data.get("token") or data.get("accessToken")
if not token:
raise ZenDaoAPIError(f"Unexpected token response: {data}")
self._token = token
self.session.headers.update({self.settings.token_header: token})
return token
def _format_password(self, password: str) -> str:
if self.settings.password_hash == "md5":
return hashlib.md5(password.encode()).hexdigest()
return password
def fetch_bugs_page(self, page: int = 1) -> List[Dict]:
params = {"page": page, "limit": self.settings.per_page}
endpoint = f"api.php/v1/products/{self.settings.product_id}/bugs"
response = self._request("get", endpoint, params=params)
payload = response.json()
bugs = payload.get("bugs") or payload.get("data")
if bugs is None:
raise ZenDaoAPIError(f"Unexpected bug payload: {payload}")
return list(bugs)
def fetch_all_bugs(self) -> List[Dict]:
all_bugs: List[Dict] = []
for page in range(1, self.settings.max_pages + 1):
page_bugs = self.fetch_bugs_page(page)
if not page_bugs:
break
all_bugs.extend(page_bugs)
if len(page_bugs) < self.settings.per_page:
break
return all_bugs
def _request(self, method: str, path: str, **kwargs) -> Response:
url = f"{self.settings.base_url}/{path.lstrip('/')}"
kwargs.setdefault("timeout", self.settings.timeout)
kwargs.setdefault("verify", self.settings.verify_ssl)
response = self.session.request(method=method, url=url, **kwargs)
response.raise_for_status()
return response
def normalize_bug(bug: Dict, fields: Sequence[str] = BUG_EXPORT_FIELDS) -> Dict[str, str]:
normalized = {}
for field in fields:
value = bug.get(field, "")
if isinstance(value, dict):
value = value.get("realname") or value.get("account") or value
normalized[field] = value if isinstance(value, str) else str(value)
return normalized
def write_json(path: Path, bugs: Iterable[Dict]) -> None:
path.write_text(json.dumps(list(bugs), ensure_ascii=False, indent=2), encoding="utf-8")
def write_csv(path: Path, bugs: Iterable[Dict], fields: Sequence[str]) -> None:
bugs = list(bugs)
if not bugs:
path.write_text("", encoding="utf-8")
return
with path.open("w", encoding="utf-8", newline="") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=fields)
writer.writeheader()
for bug in bugs:
writer.writerow(normalize_bug(bug, fields))
def run_crawler(config: Path, output: Path) -> Path:
settings = ZenDaoSettings.from_file(config)
settings.validate()
client = ZenDaoClient(settings)
client.authenticate()
bugs = client.fetch_all_bugs()
if output.suffix.lower() == ".json":
write_json(output, bugs)
else:
write_csv(output, bugs, BUG_EXPORT_FIELDS)
return output
def parse_args(argv: Sequence[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Fetch bug list from ZenDao.")
parser.add_argument("--config", type=Path, default=DEFAULT_CONFIG_PATH, help="Path to zendao_config.ini")
parser.add_argument(
"--output",
type=Path,
default=Path("bugs.csv"),
help="Output file path (.csv or .json)",
)
return parser.parse_args(argv)
def main(argv: Sequence[str] | None = None) -> None:
args = parse_args(argv or sys.argv[1:])
output = run_crawler(args.config, args.output)
print(f"Bug list saved to: {output}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,14 @@
[zendao]
base_url = http://39.170.26.156:8888
username = qiaoxinjiu
password = Qiao123456
product_id = 1
per_page = 50
max_pages = 10
verify_ssl = false
timeout = 10
auth_mode = password
api_token =
auth_endpoint = api.php/v1/tokens
token_header = Token
password_hash = md5