coverage-tool 1.0.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.
- coverage_tool/__init__.py +7 -0
- coverage_tool/analyzers/__init__.py +14 -0
- coverage_tool/analyzers/dependency.py +513 -0
- coverage_tool/converters/__init__.py +60 -0
- coverage_tool/converters/base.py +83 -0
- coverage_tool/converters/cunit_converter.py +47 -0
- coverage_tool/converters/factory.py +36 -0
- coverage_tool/converters/generic_converter.py +62 -0
- coverage_tool/converters/go_converter.py +178 -0
- coverage_tool/converters/gtest_converter.py +63 -0
- coverage_tool/core/__init__.py +18 -0
- coverage_tool/core/config.py +66 -0
- coverage_tool/core/reporter.py +270 -0
- coverage_tool/example.py +102 -0
- coverage_tool/handlers/__init__.py +13 -0
- coverage_tool/handlers/compilation.py +322 -0
- coverage_tool/handlers/env_checker.py +312 -0
- coverage_tool/main.py +559 -0
- coverage_tool/parsers/__init__.py +15 -0
- coverage_tool/parsers/junit.py +172 -0
- coverage_tool/runners/__init__.py +26 -0
- coverage_tool/runners/base.py +249 -0
- coverage_tool/runners/c_runner.py +66 -0
- coverage_tool/runners/cpp_runner.py +65 -0
- coverage_tool/runners/factory.py +41 -0
- coverage_tool/runners/go_runner.py +158 -0
- coverage_tool/runners/java_runner.py +54 -0
- coverage_tool/runners/python_runner.py +99 -0
- coverage_tool/test_removers/__init__.py +38 -0
- coverage_tool/test_removers/base.py +233 -0
- coverage_tool/test_removers/c_remover.py +210 -0
- coverage_tool/test_removers/cpp_remover.py +272 -0
- coverage_tool/test_removers/factory.py +45 -0
- coverage_tool/test_removers/go_remover.py +112 -0
- coverage_tool/test_removers/java_remover.py +216 -0
- coverage_tool/test_removers/python_remover.py +172 -0
- coverage_tool/utils/__init__.py +23 -0
- coverage_tool/utils/dependency_graph.py +52 -0
- coverage_tool/utils/file_backup.py +112 -0
- coverage_tool/utils/helpers.py +89 -0
- coverage_tool/utils/logger.py +54 -0
- coverage_tool/utils/progress.py +41 -0
- coverage_tool-1.0.0.dist-info/METADATA +484 -0
- coverage_tool-1.0.0.dist-info/RECORD +47 -0
- coverage_tool-1.0.0.dist-info/WHEEL +5 -0
- coverage_tool-1.0.0.dist-info/entry_points.txt +2 -0
- coverage_tool-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
覆盖率统计报告生成器
|
|
4
|
+
生成格式化的测试覆盖率报告
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import List, Dict, Optional
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from enum import Enum
|
|
11
|
+
import json
|
|
12
|
+
import csv
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
class ReportFormat(Enum):
|
|
16
|
+
TEXT = "text"
|
|
17
|
+
JSON = "json"
|
|
18
|
+
CSV = "csv"
|
|
19
|
+
HTML = "html"
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class CoverageStats:
|
|
23
|
+
"""覆盖率统计"""
|
|
24
|
+
language: str
|
|
25
|
+
total_tests: int = 0
|
|
26
|
+
passed_tests: int = 0
|
|
27
|
+
failed_tests: int = 0
|
|
28
|
+
error_tests: int = 0
|
|
29
|
+
skipped_tests: int = 0
|
|
30
|
+
pass_rate: float = 0.0
|
|
31
|
+
coverage_rate: float = 0.0
|
|
32
|
+
duration: float = 0.0
|
|
33
|
+
test_files: int = 0
|
|
34
|
+
source_files: int = 0
|
|
35
|
+
deleted_files: int = 0
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class CoverageReport:
|
|
39
|
+
"""完整覆盖率报告"""
|
|
40
|
+
project_name: str
|
|
41
|
+
generated_at: datetime = field(default_factory=datetime.now)
|
|
42
|
+
stats: List[CoverageStats] = field(default_factory=list)
|
|
43
|
+
total_duration: float = 0.0
|
|
44
|
+
overall_pass_rate: float = 0.0
|
|
45
|
+
overall_coverage: float = 0.0
|
|
46
|
+
deleted_files: List[str] = field(default_factory=list)
|
|
47
|
+
|
|
48
|
+
def calculate_overall_stats(self):
|
|
49
|
+
"""计算总体统计"""
|
|
50
|
+
if not self.stats:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
total_tests = sum(s.total_tests for s in self.stats)
|
|
54
|
+
total_passed = sum(s.passed_tests for s in self.stats)
|
|
55
|
+
total_failed = sum(s.failed_tests for s in self.stats)
|
|
56
|
+
total_errors = sum(s.error_tests for s in self.stats)
|
|
57
|
+
total_skipped = sum(s.skipped_tests for s in self.stats)
|
|
58
|
+
total_coverage = sum(s.coverage_rate for s in self.stats)
|
|
59
|
+
self.total_duration = sum(s.duration for s in self.stats)
|
|
60
|
+
|
|
61
|
+
if total_tests > 0:
|
|
62
|
+
self.overall_pass_rate = ((total_passed / total_tests) * 100) if total_tests > 0 else 0.0
|
|
63
|
+
else:
|
|
64
|
+
self.overall_pass_rate = 0.0
|
|
65
|
+
|
|
66
|
+
self.overall_coverage = (total_coverage / len(self.stats)) if self.stats else 0.0
|
|
67
|
+
|
|
68
|
+
for stat in self.stats:
|
|
69
|
+
self.deleted_files.extend([stat.language] * stat.deleted_files)
|
|
70
|
+
|
|
71
|
+
class CoverageReporter:
|
|
72
|
+
"""覆盖率报告生成器"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, report: CoverageReport):
|
|
75
|
+
self.report = report
|
|
76
|
+
|
|
77
|
+
def generate(self, output_file: str, format: ReportFormat = ReportFormat.TEXT) -> str:
|
|
78
|
+
"""生成报告"""
|
|
79
|
+
if format == ReportFormat.JSON:
|
|
80
|
+
return self._generate_json(output_file)
|
|
81
|
+
elif format == ReportFormat.CSV:
|
|
82
|
+
return self._generate_csv(output_file)
|
|
83
|
+
elif format == ReportFormat.HTML:
|
|
84
|
+
return self._generate_html(output_file)
|
|
85
|
+
else:
|
|
86
|
+
return self._generate_text(output_file)
|
|
87
|
+
|
|
88
|
+
def _generate_text(self, output_file: str) -> str:
|
|
89
|
+
"""生成文本报告"""
|
|
90
|
+
lines = []
|
|
91
|
+
lines.append("=" * 80)
|
|
92
|
+
lines.append(f"单测覆盖率报告")
|
|
93
|
+
lines.append(f"项目: {self.report.project_name}")
|
|
94
|
+
lines.append(f"生成时间: {self.report.generated_at.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
95
|
+
lines.append("=" * 80)
|
|
96
|
+
lines.append("")
|
|
97
|
+
|
|
98
|
+
for stat in self.report.stats:
|
|
99
|
+
lines.append(f"语言: {stat.language}")
|
|
100
|
+
lines.append("-" * 40)
|
|
101
|
+
lines.append(f" 测试用例数: {stat.total_tests}")
|
|
102
|
+
lines.append(f" 通过: {stat.passed_tests}")
|
|
103
|
+
lines.append(f" 失败: {stat.failed_tests}")
|
|
104
|
+
lines.append(f" 错误: {stat.error_tests}")
|
|
105
|
+
lines.append(f" 跳过: {stat.skipped_tests}")
|
|
106
|
+
lines.append(f" 通过率: {stat.pass_rate:.2f}%")
|
|
107
|
+
lines.append(f" 覆盖率: {stat.coverage_rate:.2f}%")
|
|
108
|
+
lines.append(f" 执行时间: {stat.duration:.2f}s")
|
|
109
|
+
lines.append(f" 测试文件数: {stat.test_files}")
|
|
110
|
+
lines.append(f" 源文件数: {stat.source_files}")
|
|
111
|
+
lines.append(f" 删除文件数: {stat.deleted_files}")
|
|
112
|
+
lines.append("")
|
|
113
|
+
|
|
114
|
+
lines.append("=" * 80)
|
|
115
|
+
lines.append("总体统计")
|
|
116
|
+
lines.append("=" * 80)
|
|
117
|
+
lines.append(f"总执行时间: {self.report.total_duration:.2f}s")
|
|
118
|
+
lines.append(f"总体通过率: {self.report.overall_pass_rate:.2f}%")
|
|
119
|
+
lines.append(f"总体覆盖率: {self.report.overall_coverage:.2f}%")
|
|
120
|
+
lines.append("")
|
|
121
|
+
|
|
122
|
+
if self.report.deleted_files:
|
|
123
|
+
lines.append(f"已删除文件: {len(self.report.deleted_files)}个")
|
|
124
|
+
|
|
125
|
+
report_content = '\n'.join(lines)
|
|
126
|
+
|
|
127
|
+
if output_file:
|
|
128
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
129
|
+
f.write(report_content)
|
|
130
|
+
|
|
131
|
+
return report_content
|
|
132
|
+
|
|
133
|
+
def _generate_json(self, output_file: str) -> str:
|
|
134
|
+
"""生成JSON报告"""
|
|
135
|
+
report_data = {
|
|
136
|
+
"project_name": self.report.project_name,
|
|
137
|
+
"generated_at": self.report.generated_at.isoformat(),
|
|
138
|
+
"total_duration": self.report.total_duration,
|
|
139
|
+
"overall_pass_rate": self.report.overall_pass_rate,
|
|
140
|
+
"overall_coverage": self.report.overall_coverage,
|
|
141
|
+
"stats": [
|
|
142
|
+
{
|
|
143
|
+
"language": stat.language,
|
|
144
|
+
"total_tests": stat.total_tests,
|
|
145
|
+
"passed_tests": stat.passed_tests,
|
|
146
|
+
"failed_tests": stat.failed_tests,
|
|
147
|
+
"error_tests": stat.error_tests,
|
|
148
|
+
"skipped_tests": stat.skipped_tests,
|
|
149
|
+
"pass_rate": stat.pass_rate,
|
|
150
|
+
"coverage_rate": stat.coverage_rate,
|
|
151
|
+
"duration": stat.duration,
|
|
152
|
+
"test_files": stat.test_files,
|
|
153
|
+
"source_files": stat.source_files,
|
|
154
|
+
"deleted_files": stat.deleted_files
|
|
155
|
+
}
|
|
156
|
+
for stat in self.report.stats
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
report_content = json.dumps(report_data, ensure_ascii=False, indent=2)
|
|
161
|
+
|
|
162
|
+
if output_file:
|
|
163
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
164
|
+
f.write(report_content)
|
|
165
|
+
|
|
166
|
+
return report_content
|
|
167
|
+
|
|
168
|
+
def _generate_csv(self, output_file: str) -> str:
|
|
169
|
+
"""生成CSV报告"""
|
|
170
|
+
lines = []
|
|
171
|
+
headers = [
|
|
172
|
+
"语言", "测试用例数", "通过", "失败", "错误", "跳过",
|
|
173
|
+
"通过率", "覆盖率", "执行时间", "测试文件数", "源文件数", "删除文件数"
|
|
174
|
+
]
|
|
175
|
+
lines.append(','.join(headers))
|
|
176
|
+
|
|
177
|
+
for stat in self.report.stats:
|
|
178
|
+
row = [
|
|
179
|
+
stat.language,
|
|
180
|
+
str(stat.total_tests),
|
|
181
|
+
str(stat.passed_tests),
|
|
182
|
+
str(stat.failed_tests),
|
|
183
|
+
str(stat.error_tests),
|
|
184
|
+
str(stat.skipped_tests),
|
|
185
|
+
f"{stat.pass_rate:.2f}",
|
|
186
|
+
f"{stat.coverage_rate:.2f}",
|
|
187
|
+
f"{stat.duration:.2f}",
|
|
188
|
+
str(stat.test_files),
|
|
189
|
+
str(stat.source_files),
|
|
190
|
+
str(stat.deleted_files)
|
|
191
|
+
]
|
|
192
|
+
lines.append(','.join(row))
|
|
193
|
+
|
|
194
|
+
report_content = '\n'.join(lines)
|
|
195
|
+
|
|
196
|
+
if output_file:
|
|
197
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
198
|
+
f.write(report_content)
|
|
199
|
+
|
|
200
|
+
return report_content
|
|
201
|
+
|
|
202
|
+
def _generate_html(self, output_file: str) -> str:
|
|
203
|
+
"""生成HTML报告"""
|
|
204
|
+
html = f"""<!DOCTYPE html>
|
|
205
|
+
<html>
|
|
206
|
+
<head>
|
|
207
|
+
<title>单测覆盖率报告 - {self.report.project_name}</title>
|
|
208
|
+
<style>
|
|
209
|
+
body {{ font-family: Arial, sans-serif; margin: 20px; }}
|
|
210
|
+
table {{ border-collapse: collapse; width: 100%; margin-bottom: 20px; }}
|
|
211
|
+
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
|
|
212
|
+
th {{ background-color: #4CAF50; color: white; }}
|
|
213
|
+
tr:nth-child(even) {{ background-color: #f2f2f2; }}
|
|
214
|
+
.summary {{ background-color: #f9f9f9; padding: 15px; border-radius: 5px; }}
|
|
215
|
+
h1 {{ color: #333; }}
|
|
216
|
+
h2 {{ color: #555; }}
|
|
217
|
+
</style>
|
|
218
|
+
</head>
|
|
219
|
+
<body>
|
|
220
|
+
<h1>单测覆盖率报告</h1>
|
|
221
|
+
<p><strong>项目:</strong> {self.report.project_name}</p>
|
|
222
|
+
<p><strong>生成时间:</strong> {self.report.generated_at.strftime('%Y-%m-%d %H:%M:%S')}</p>
|
|
223
|
+
|
|
224
|
+
<h2>各语言统计</h2>
|
|
225
|
+
<table>
|
|
226
|
+
<tr>
|
|
227
|
+
<th>语言</th>
|
|
228
|
+
<th>测试用例</th>
|
|
229
|
+
<th>通过</th>
|
|
230
|
+
<th>失败</th>
|
|
231
|
+
<th>错误</th>
|
|
232
|
+
<th>跳过</th>
|
|
233
|
+
<th>通过率</th>
|
|
234
|
+
<th>覆盖率</th>
|
|
235
|
+
<th>执行时间</th>
|
|
236
|
+
</tr>
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
for stat in self.report.stats:
|
|
240
|
+
html += f""" <tr>
|
|
241
|
+
<td>{stat.language}</td>
|
|
242
|
+
<td>{stat.total_tests}</td>
|
|
243
|
+
<td>{stat.passed_tests}</td>
|
|
244
|
+
<td>{stat.failed_tests}</td>
|
|
245
|
+
<td>{stat.error_tests}</td>
|
|
246
|
+
<td>{stat.skipped_tests}</td>
|
|
247
|
+
<td>{stat.pass_rate:.2f}%</td>
|
|
248
|
+
<td>{stat.coverage_rate:.2f}%</td>
|
|
249
|
+
<td>{stat.duration:.2f}s</td>
|
|
250
|
+
</tr>
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
html += """ </table>
|
|
254
|
+
|
|
255
|
+
<h2>总体统计</h2>
|
|
256
|
+
<div class="summary">
|
|
257
|
+
<p><strong>总执行时间:</strong> """ + f"{self.report.total_duration:.2f}s</p>"
|
|
258
|
+
html += f""" <p><strong>总体通过率:</strong> {self.report.overall_pass_rate:.2f}%</p>
|
|
259
|
+
<p><strong>总体覆盖率:</strong> {self.report.overall_coverage:.2f}%</p>
|
|
260
|
+
</div>
|
|
261
|
+
</body>
|
|
262
|
+
</html>"""
|
|
263
|
+
|
|
264
|
+
report_content = html
|
|
265
|
+
|
|
266
|
+
if output_file:
|
|
267
|
+
with open(output_file, 'w', encoding='utf-8') as f:
|
|
268
|
+
f.write(report_content)
|
|
269
|
+
|
|
270
|
+
return report_content
|
coverage_tool/example.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
使用示例 - 演示覆盖率工具的基本用法
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
|
|
10
|
+
from coverage_tool.main import CoverageTool, ToolConfig
|
|
11
|
+
from coverage_tool.core import Language, ReportFormat
|
|
12
|
+
|
|
13
|
+
def create_sample_python_project(project_dir):
|
|
14
|
+
"""创建一个示例Python项目"""
|
|
15
|
+
# 创建主模块
|
|
16
|
+
main_file = os.path.join(project_dir, "main.py")
|
|
17
|
+
with open(main_file, 'w') as f:
|
|
18
|
+
f.write("""
|
|
19
|
+
def add(a, b):
|
|
20
|
+
return a + b
|
|
21
|
+
|
|
22
|
+
def subtract(a, b):
|
|
23
|
+
return a - b
|
|
24
|
+
|
|
25
|
+
def multiply(a, b):
|
|
26
|
+
return a * b
|
|
27
|
+
""")
|
|
28
|
+
|
|
29
|
+
# 创建测试文件
|
|
30
|
+
test_file = os.path.join(project_dir, "test_main.py")
|
|
31
|
+
with open(test_file, 'w') as f:
|
|
32
|
+
f.write("""
|
|
33
|
+
import pytest
|
|
34
|
+
from main import add, subtract, multiply
|
|
35
|
+
|
|
36
|
+
def test_add():
|
|
37
|
+
assert add(2, 3) == 5
|
|
38
|
+
assert add(-1, 1) == 0
|
|
39
|
+
|
|
40
|
+
def test_subtract():
|
|
41
|
+
assert subtract(5, 3) == 2
|
|
42
|
+
assert subtract(0, 5) == -5
|
|
43
|
+
|
|
44
|
+
def test_multiply():
|
|
45
|
+
assert multiply(3, 4) == 12
|
|
46
|
+
assert multiply(-2, 3) == -6
|
|
47
|
+
|
|
48
|
+
# 这个测试会失败
|
|
49
|
+
def test_fail():
|
|
50
|
+
assert add(1, 1) == 3 # 错误的断言
|
|
51
|
+
""")
|
|
52
|
+
|
|
53
|
+
print(f"✓ 创建示例Python项目: {project_dir}")
|
|
54
|
+
return project_dir
|
|
55
|
+
|
|
56
|
+
def run_example():
|
|
57
|
+
"""运行示例"""
|
|
58
|
+
print("=" * 60)
|
|
59
|
+
print("覆盖率工具使用示例")
|
|
60
|
+
print("=" * 60)
|
|
61
|
+
|
|
62
|
+
# 创建临时项目
|
|
63
|
+
with tempfile.TemporaryDirectory() as project_dir:
|
|
64
|
+
create_sample_python_project(project_dir)
|
|
65
|
+
|
|
66
|
+
print("\n配置工具...")
|
|
67
|
+
config = ToolConfig(
|
|
68
|
+
language=Language.PYTHON,
|
|
69
|
+
target_dir=project_dir,
|
|
70
|
+
output_file="coverage_report",
|
|
71
|
+
report_format=ReportFormat.TEXT,
|
|
72
|
+
max_compile_iterations=3,
|
|
73
|
+
max_cleanup_iterations=3
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
print("运行覆盖率分析...")
|
|
77
|
+
print("-" * 60)
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
with CoverageTool(config) as tool:
|
|
81
|
+
report = tool.run()
|
|
82
|
+
|
|
83
|
+
print("\n" + "=" * 60)
|
|
84
|
+
print("分析结果")
|
|
85
|
+
print("=" * 60)
|
|
86
|
+
|
|
87
|
+
if report.stats:
|
|
88
|
+
stat = report.stats[0]
|
|
89
|
+
print(f"语言: {stat.language}")
|
|
90
|
+
print(f"测试用例: {stat.total_tests}")
|
|
91
|
+
print(f"通过: {stat.passed_tests}")
|
|
92
|
+
print(f"失败: {stat.failed_tests}")
|
|
93
|
+
print(f"覆盖率: {stat.coverage_rate:.2f}%")
|
|
94
|
+
print(f"执行时间: {stat.duration:.2f}s")
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print(f"\n错误: {e}")
|
|
98
|
+
import traceback
|
|
99
|
+
traceback.print_exc()
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
run_example()
|