iflow-mcp_galaxyxieyu_api-auto-test 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,160 @@
1
+ # @time: 2024-09-14
2
+ # @author: xiaoqq
3
+
4
+ import subprocess
5
+ import os
6
+ import shutil
7
+ import json
8
+ import platform
9
+ from datetime import datetime
10
+ from atf.core.log_manager import log
11
+
12
+
13
+ class ReportGenerator:
14
+ """
15
+ 测试报告生成器,支持生成 Allure 报告和 pytest-html 报告。
16
+ """
17
+
18
+ def __init__(self, report_type, env):
19
+ """
20
+ 初始化 ReportGenerator 实例,设置报告类型和测试环境。
21
+
22
+ :param report_type: 报告类型 ('allure' 或 'html')
23
+ :param env: 测试环境名称
24
+ """
25
+ self.report_type = report_type
26
+ self.env = env
27
+ self.day_timestamp = datetime.now().strftime('%Y%m%d')
28
+ self.sec_timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
29
+ self.reports_dir = os.path.join('html-reports', f'{self.day_timestamp}')
30
+ os.makedirs(self.reports_dir, exist_ok=True)
31
+
32
+ # 定义测试结果数据目录和历史数据目录
33
+ self.allure_results_dir = os.path.join('html-reports', 'allure-results')
34
+ self.allure_history_dir = os.path.join('html-reports', 'allure-history')
35
+ os.makedirs(self.allure_results_dir, exist_ok=True)
36
+ os.makedirs(self.allure_history_dir, exist_ok=True)
37
+ self.allure_report_dir = None
38
+ self.html_report_path = None
39
+
40
+ def prepare_report(self):
41
+ """
42
+ 准备生成报告的环境,根据报告类型调用相应的准备方法。
43
+ """
44
+ if self.report_type == 'allure':
45
+ self.prepare_allure_report()
46
+ elif self.report_type == 'html':
47
+ self.prepare_pytest_html_report()
48
+ else:
49
+ raise ValueError(f"不支持的报告类型: {self.report_type}")
50
+
51
+ def prepare_allure_report(self):
52
+ """
53
+ 准备 Allure 报告所需的目录和文件。
54
+ """
55
+ self.allure_report_dir = os.path.join(self.reports_dir, f'report_{self.sec_timestamp}_allure')
56
+ os.makedirs(self.allure_report_dir, exist_ok=True)
57
+ self.clean_allure_results()
58
+ self.write_environment_file()
59
+ self.write_executor_file()
60
+ self.write_categories_file()
61
+
62
+ def prepare_pytest_html_report(self):
63
+ """
64
+ 准备 pytest-html 报告的路径。
65
+ """
66
+ self.html_report_path = os.path.join(self.reports_dir, f'report_{self.sec_timestamp}.html')
67
+
68
+ def clean_allure_results(self):
69
+ """
70
+ 清除旧的 Allure 测试结果数据。
71
+ """
72
+ for filename in os.listdir(self.allure_results_dir):
73
+ file_path = os.path.join(self.allure_results_dir, filename)
74
+ try:
75
+ if os.path.isfile(file_path) or os.path.islink(file_path):
76
+ os.unlink(file_path)
77
+ elif os.path.isdir(file_path):
78
+ shutil.rmtree(file_path)
79
+ except Exception as e:
80
+ log.error(f'删除文件 {file_path} 时出错: {e}')
81
+
82
+ def write_environment_file(self):
83
+ """
84
+ 生成 environment.properties 文件,用于 Allure 报告中显示环境信息。
85
+ """
86
+ environment_file = os.path.join(self.allure_results_dir, 'environment.properties')
87
+ with open(environment_file, 'w') as f:
88
+ f.write(f"Environment={self.env}\n")
89
+ f.write(f"PythonVersion={platform.python_version()}\n")
90
+ f.write(f"Platform={platform.system()}\n")
91
+ f.write(f"PlatformVersion={platform.version()}\n")
92
+
93
+ def write_executor_file(self):
94
+ """
95
+ 生成 executor.json 文件。
96
+ """
97
+ executor_file = os.path.join(self.allure_results_dir, 'executor.json')
98
+ executor_info = {
99
+ "name": "Local Machine", # 或者是 Jenkins、GitLab CI 等
100
+ "type": "local", # 本地执行,或者是 Jenkins 等工具
101
+ "reportName": "Allure Report",
102
+ "buildUrl": "http://localhost:8080", # 如果有 CI/CD 工具,可以填写其 URL
103
+ "buildId": "12345", # 如果有 Jenkins 之类的,填写 Build ID
104
+ "reportUrl": "http://localhost:8080/allure-report" # 报告 URL
105
+ }
106
+ with open(executor_file, 'w') as f:
107
+ json.dump(executor_info, f, indent=4)
108
+
109
+ def write_categories_file(self):
110
+ """
111
+ 生成 categories.json 文件。
112
+ """
113
+ categories_file = os.path.join(self.allure_results_dir, 'categories.json')
114
+ categories = [
115
+ {"name": "Test Failures", "matchedStatuses": ["failed"]},
116
+ {"name": "Broken Tests", "matchedStatuses": ["broken"]},
117
+ {"name": "Skipped Tests", "matchedStatuses": ["skipped"]}
118
+ ]
119
+ with open(categories_file, 'w') as f:
120
+ json.dump(categories, f, indent=4)
121
+
122
+ def copytree(self, src, dst):
123
+ """
124
+ 兼容旧版本的 shutil.copytree 方法,不使用 dirs_exist_ok 参数。
125
+ :param src: 源目录
126
+ :param dst: 目标目录
127
+ """
128
+ if os.path.exists(dst):
129
+ shutil.rmtree(dst) # 如果目标目录存在,先删除
130
+ shutil.copytree(src, dst) # 复制源目录到目标
131
+
132
+ def generate_allure_report(self):
133
+ """
134
+ 生成 Allure HTML 报告。
135
+ """
136
+ log.info(f'正在生成 Allure 测试报告......')
137
+ allure_exe = r'D:\download-dir\allure-2.30.0\allure-2.30.0\bin\allure.bat'
138
+ html_report = os.path.join(self.allure_report_dir, 'html')
139
+
140
+ # 如果历史数据存在,则拷贝历史数据到结果目录
141
+ history_dir = os.path.join(self.allure_results_dir, 'history')
142
+ if os.path.exists(os.path.join(self.allure_history_dir, 'history')):
143
+ self.copytree(os.path.join(self.allure_history_dir, 'history'), history_dir)
144
+
145
+ # 生成 Allure 报告
146
+ subprocess.run([allure_exe, 'generate', self.allure_results_dir, '-o', html_report, '--clean'], check=True)
147
+
148
+ # 复制生成后的历史数据
149
+ generated_history_dir = os.path.join(html_report, 'history')
150
+ if os.path.exists(generated_history_dir):
151
+ self.copytree(generated_history_dir, os.path.join(self.allure_history_dir, 'history'))
152
+
153
+ log.info(f"生成的报告位于: {html_report}")
154
+
155
+ # def generate_pytest_html_report(self):
156
+ # """
157
+ # 生成 pytest-html 报告。
158
+ # """
159
+ # log.info(f'正在生成 pytest-html 测试报告......')
160
+ # # 生成 pytest-html 报告的具体实现逻辑,省略...
@@ -0,0 +1,106 @@
1
+ # @time: 2024-09-10
2
+ # @author: xiaoqq
3
+
4
+ import mysql.connector
5
+ import threading
6
+ import requests
7
+ from atf.core.log_manager import log
8
+
9
+
10
+ class TeardownHandler:
11
+ """测试用例后置操作处理器"""
12
+ def __init__(self):
13
+ self.connection = None
14
+ self.lock = threading.Lock()
15
+
16
+ def __enter__(self):
17
+ return self
18
+
19
+ def __exit__(self, exc_type, exc_val, exc_tb):
20
+ if self.connection:
21
+ self.connection.close()
22
+
23
+ def handle_teardown(self, teardown_step, db_config=None):
24
+ """
25
+ 处理 teardown 步骤,根据 operation_type 区分处理方式
26
+ :param teardown_step: teardown 中带数据库操作步骤
27
+ :param db_config: 数据库配置字典,包含 host, user, password, database 等信息
28
+ """
29
+ if db_config is None:
30
+ raise ValueError("数据库配置 db_config 不能为空,因为有数据库相关操作")
31
+ self._handle_db_operation(teardown_step, db_config)
32
+
33
+ def _handle_api_request(self, step):
34
+ """
35
+ 处理 API 请求类型的 teardown 步骤
36
+ :param step: 单个 teardown 步骤,包含 API 请求的详细信息
37
+ """
38
+ path = step.get('path')
39
+ method = step.get('method', 'GET').upper()
40
+ headers = step.get('headers', {})
41
+ data = step.get('data', {})
42
+
43
+ log.info(f"执行 API 请求: {method} {path} 数据: {data}")
44
+
45
+ # 执行 API 请求
46
+ if method == 'POST':
47
+ response = requests.post(path, json=data, headers=headers)
48
+ elif method == 'GET':
49
+ response = requests.get(path, headers=headers, params=data)
50
+ else:
51
+ raise ValueError(f"不支持的 HTTP 方法: {method}")
52
+
53
+ log.info(f"API 响应状态码: {response.status_code}")
54
+ response.raise_for_status() # 如果请求失败则抛出异常
55
+
56
+ def _handle_db_operation(self, step, db_config):
57
+ """
58
+ 处理数据库操作类型的 teardown 步骤
59
+ :param step: 单个 teardown 步骤,包含数据库操作的详细信息
60
+ :param db_config: 数据库配置字典,包含 host, user, password, database 等信息
61
+ """
62
+ query = step.get('query')
63
+ expected = step.get('expected')
64
+
65
+ log.info(f"执行数据库操作: {query}")
66
+
67
+ # 建立数据库连接(如果还没有连接的话)
68
+ if not self.connection:
69
+ self.connection = mysql.connector.connect(**db_config)
70
+
71
+ with self.lock:
72
+ cursor = self.connection.cursor()
73
+
74
+ # 检查是否是查询语句
75
+ if query.strip().lower().startswith("select"):
76
+ cursor.execute(query)
77
+ result = cursor.fetchone()
78
+
79
+ if expected is not None:
80
+ assert result[0] == expected, f"Expected {expected}, but got {result[0]}"
81
+ else:
82
+ log.info(f"查询结果: {result}")
83
+ else:
84
+ # 非查询操作 (如 DELETE, UPDATE, INSERT)
85
+ cursor.execute(query)
86
+ affected_rows = cursor.rowcount
87
+ log.info(f"受影响的行数: {affected_rows}")
88
+
89
+ # 对于非查询操作,验证是否有行受影响 (expected 为 True 表示至少一行被影响)
90
+ if expected is not None:
91
+ assert (affected_rows > 0) == expected, f"Expected operation to affect rows: {expected}, but got {affected_rows > 0}"
92
+
93
+ cursor.close()
94
+
95
+ def _execute_query(self, query):
96
+ """
97
+ 执行 MySQL 查询并返回结果
98
+ :param query: 要执行的 MySQL 查询
99
+ :return: 查询结果
100
+ """
101
+ with self.lock:
102
+ cursor = self.connection.cursor()
103
+ cursor.execute(query)
104
+ result = cursor.fetchone()
105
+ cursor.close()
106
+ return result[0] if result else None
atf/mcp/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # MCP Server Package