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,322 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
智能编译失败处理器
|
|
4
|
+
支持渐进式删除策略和依赖分析
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import shutil
|
|
10
|
+
from typing import List, Tuple, Optional, Dict, Set
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from enum import Enum
|
|
14
|
+
|
|
15
|
+
from coverage_tool.utils import FileBackupManager, DependencyGraph, Logger, parse_error_locations, safe_delete_file
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FilePriority(Enum):
|
|
19
|
+
"""文件优先级"""
|
|
20
|
+
LOW = 1 # 可以安全删除的文件(如测试文件、示例文件)
|
|
21
|
+
MEDIUM = 2 # 普通源文件
|
|
22
|
+
HIGH = 3 # 核心文件(如main文件、配置文件)
|
|
23
|
+
CRITICAL = 4 # 绝对不能删除的文件
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class FileInfo:
|
|
28
|
+
"""文件信息"""
|
|
29
|
+
path: str
|
|
30
|
+
priority: FilePriority
|
|
31
|
+
error_count: int = 0
|
|
32
|
+
is_test_file: bool = False
|
|
33
|
+
is_source_file: bool = False
|
|
34
|
+
dependents: List[str] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SmartCompilationHandler:
|
|
38
|
+
"""智能编译失败处理器"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, target_dir: str, logger: Optional[Logger] = None):
|
|
41
|
+
self.target_dir = target_dir
|
|
42
|
+
self.logger = logger or Logger()
|
|
43
|
+
self.backup_manager = FileBackupManager()
|
|
44
|
+
self.dependency_graph = DependencyGraph()
|
|
45
|
+
self.deleted_files: List[str] = []
|
|
46
|
+
self.compilation_history: List[Dict] = []
|
|
47
|
+
|
|
48
|
+
# 文件优先级模式
|
|
49
|
+
self.priority_patterns = {
|
|
50
|
+
FilePriority.LOW: [
|
|
51
|
+
r'.*test.*\.(py|java|go|c|cpp|h|hpp)$',
|
|
52
|
+
r'.*example.*\.(py|java|go|c|cpp)$',
|
|
53
|
+
r'.*demo.*\.(py|java|go|c|cpp)$',
|
|
54
|
+
r'.*mock.*\.(py|java|go|c|cpp)$',
|
|
55
|
+
r'.*stub.*\.(py|java|go|c|cpp)$',
|
|
56
|
+
],
|
|
57
|
+
FilePriority.HIGH: [
|
|
58
|
+
r'^main\.(py|java|go|c|cpp)$',
|
|
59
|
+
r'^index\.(py|java|go|c|cpp)$',
|
|
60
|
+
r'^app\.(py|java|go|c|cpp)$',
|
|
61
|
+
r'^setup\.py$',
|
|
62
|
+
r'^[Rr]eadme\.md$',
|
|
63
|
+
r'^go\.mod$',
|
|
64
|
+
r'^pom\.xml$',
|
|
65
|
+
r'^build\.gradle$',
|
|
66
|
+
r'^Makefile$',
|
|
67
|
+
r'^CMakeLists\.txt$',
|
|
68
|
+
],
|
|
69
|
+
FilePriority.CRITICAL: [
|
|
70
|
+
r'.*\.git.*',
|
|
71
|
+
r'.*\.svn.*',
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def analyze_compilation_errors(self, errors: List[str]) -> List[FileInfo]:
|
|
76
|
+
"""分析编译错误,提取问题文件"""
|
|
77
|
+
error_locations = parse_error_locations(errors, self.target_dir)
|
|
78
|
+
|
|
79
|
+
file_infos = []
|
|
80
|
+
for file_path, line_nums in error_locations.items():
|
|
81
|
+
if os.path.exists(file_path):
|
|
82
|
+
info = FileInfo(
|
|
83
|
+
path=file_path,
|
|
84
|
+
priority=self._determine_priority(file_path),
|
|
85
|
+
error_count=len(line_nums),
|
|
86
|
+
is_test_file=self._is_test_file(file_path),
|
|
87
|
+
is_source_file=self._is_source_file(file_path)
|
|
88
|
+
)
|
|
89
|
+
file_infos.append(info)
|
|
90
|
+
|
|
91
|
+
# 按优先级和错误数量排序(优先级低的先删除)
|
|
92
|
+
file_infos.sort(key=lambda x: (x.priority.value, -x.error_count))
|
|
93
|
+
|
|
94
|
+
return file_infos
|
|
95
|
+
|
|
96
|
+
def _determine_priority(self, file_path: str) -> FilePriority:
|
|
97
|
+
"""确定文件优先级"""
|
|
98
|
+
file_name = os.path.basename(file_path)
|
|
99
|
+
|
|
100
|
+
# 检查CRITICAL模式
|
|
101
|
+
for pattern in self.priority_patterns[FilePriority.CRITICAL]:
|
|
102
|
+
if re.match(pattern, file_name, re.IGNORECASE):
|
|
103
|
+
return FilePriority.CRITICAL
|
|
104
|
+
|
|
105
|
+
# 检查HIGH模式
|
|
106
|
+
for pattern in self.priority_patterns[FilePriority.HIGH]:
|
|
107
|
+
if re.match(pattern, file_name, re.IGNORECASE):
|
|
108
|
+
return FilePriority.HIGH
|
|
109
|
+
|
|
110
|
+
# 检查LOW模式
|
|
111
|
+
for pattern in self.priority_patterns[FilePriority.LOW]:
|
|
112
|
+
if re.match(pattern, file_name, re.IGNORECASE):
|
|
113
|
+
return FilePriority.LOW
|
|
114
|
+
|
|
115
|
+
return FilePriority.MEDIUM
|
|
116
|
+
|
|
117
|
+
def _is_test_file(self, file_path: str) -> bool:
|
|
118
|
+
"""检查是否是测试文件"""
|
|
119
|
+
test_patterns = [
|
|
120
|
+
r'.*test.*\.(py|java|go|c|cpp)$',
|
|
121
|
+
r'.*_test\.(py|java|go|c|cpp)$',
|
|
122
|
+
r'test_.*\.py$',
|
|
123
|
+
r'.*Test\.java$',
|
|
124
|
+
]
|
|
125
|
+
file_name = os.path.basename(file_path)
|
|
126
|
+
for pattern in test_patterns:
|
|
127
|
+
if re.match(pattern, file_name, re.IGNORECASE):
|
|
128
|
+
return True
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
def _is_source_file(self, file_path: str) -> bool:
|
|
132
|
+
"""检查是否是源文件"""
|
|
133
|
+
source_extensions = ['.py', '.java', '.go', '.c', '.cpp', '.h', '.hpp']
|
|
134
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
135
|
+
return ext in source_extensions
|
|
136
|
+
|
|
137
|
+
def handle_compilation_failure(
|
|
138
|
+
self,
|
|
139
|
+
errors: List[str],
|
|
140
|
+
compile_func,
|
|
141
|
+
max_iterations: int = 10
|
|
142
|
+
) -> Tuple[bool, List[str], List[str]]:
|
|
143
|
+
"""
|
|
144
|
+
处理编译失败,使用渐进式删除策略
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
errors: 编译错误列表
|
|
148
|
+
compile_func: 编译函数,接收target_dir,返回(success, errors)
|
|
149
|
+
max_iterations: 最大尝试次数
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
(编译是否成功, 被删除的文件列表, 剩余的编译错误)
|
|
153
|
+
"""
|
|
154
|
+
self.logger.info(f"开始处理编译失败,共 {len(errors)} 个错误")
|
|
155
|
+
|
|
156
|
+
iteration = 0
|
|
157
|
+
current_errors = errors.copy()
|
|
158
|
+
|
|
159
|
+
while iteration < max_iterations and current_errors:
|
|
160
|
+
iteration += 1
|
|
161
|
+
self.logger.info(f"编译修复迭代 {iteration}/{max_iterations}")
|
|
162
|
+
|
|
163
|
+
# 分析错误
|
|
164
|
+
file_infos = self.analyze_compilation_errors(current_errors)
|
|
165
|
+
|
|
166
|
+
if not file_infos:
|
|
167
|
+
self.logger.warning("无法从错误信息中识别问题文件")
|
|
168
|
+
break
|
|
169
|
+
|
|
170
|
+
# 过滤掉不能删除的文件
|
|
171
|
+
deletable_files = [
|
|
172
|
+
info for info in file_infos
|
|
173
|
+
if info.priority not in [FilePriority.CRITICAL, FilePriority.HIGH]
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
if not deletable_files:
|
|
177
|
+
self.logger.warning("没有可安全删除的文件")
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
# 选择要删除的文件(每次最多删除3个,避免过度删除)
|
|
181
|
+
files_to_delete = deletable_files[:3]
|
|
182
|
+
|
|
183
|
+
# 检查依赖关系
|
|
184
|
+
for file_info in files_to_delete:
|
|
185
|
+
impacted = self.dependency_graph.get_impact_files(file_info.path)
|
|
186
|
+
if impacted:
|
|
187
|
+
self.logger.warning(
|
|
188
|
+
f"删除 {file_info.path} 会影响 {len(impacted)} 个文件"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# 备份并删除文件
|
|
192
|
+
deleted_in_iteration = []
|
|
193
|
+
for file_info in files_to_delete:
|
|
194
|
+
if safe_delete_file(file_info.path, self.backup_manager):
|
|
195
|
+
deleted_in_iteration.append(file_info.path)
|
|
196
|
+
self.deleted_files.append(file_info.path)
|
|
197
|
+
self.logger.info(f"已删除文件: {file_info.path}")
|
|
198
|
+
|
|
199
|
+
if not deleted_in_iteration:
|
|
200
|
+
self.logger.warning("本轮未能删除任何文件")
|
|
201
|
+
break
|
|
202
|
+
|
|
203
|
+
# 记录编译历史
|
|
204
|
+
self.compilation_history.append({
|
|
205
|
+
'iteration': iteration,
|
|
206
|
+
'deleted_files': deleted_in_iteration,
|
|
207
|
+
'error_count_before': len(current_errors)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
# 尝试重新编译
|
|
211
|
+
success, new_errors = compile_func(self.target_dir)
|
|
212
|
+
|
|
213
|
+
if success:
|
|
214
|
+
self.logger.info(f"编译成功!共进行了 {iteration} 轮修复")
|
|
215
|
+
return True, self.deleted_files, []
|
|
216
|
+
|
|
217
|
+
# 检查错误是否减少
|
|
218
|
+
if len(new_errors) >= len(current_errors):
|
|
219
|
+
self.logger.warning(
|
|
220
|
+
f"错误数量未减少 ({len(current_errors)} -> {len(new_errors)}),"
|
|
221
|
+
"可能需要更激进的删除策略"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
current_errors = new_errors
|
|
225
|
+
|
|
226
|
+
# 达到最大迭代次数仍未成功
|
|
227
|
+
self.logger.error(f"编译修复失败,已达到最大迭代次数 {max_iterations}")
|
|
228
|
+
return False, self.deleted_files, current_errors
|
|
229
|
+
|
|
230
|
+
def restore_deleted_files(self) -> List[str]:
|
|
231
|
+
"""恢复所有已删除的文件"""
|
|
232
|
+
restored = []
|
|
233
|
+
for file_path in self.deleted_files:
|
|
234
|
+
if self.backup_manager.restore_file(file_path):
|
|
235
|
+
restored.append(file_path)
|
|
236
|
+
self.logger.info(f"已恢复文件: {file_path}")
|
|
237
|
+
self.deleted_files.clear()
|
|
238
|
+
return restored
|
|
239
|
+
|
|
240
|
+
def get_deleted_files(self) -> List[str]:
|
|
241
|
+
"""获取已删除的文件列表"""
|
|
242
|
+
return self.deleted_files.copy()
|
|
243
|
+
|
|
244
|
+
def get_compilation_history(self) -> List[Dict]:
|
|
245
|
+
"""获取编译历史记录"""
|
|
246
|
+
return self.compilation_history.copy()
|
|
247
|
+
|
|
248
|
+
def cleanup(self):
|
|
249
|
+
"""清理资源"""
|
|
250
|
+
self.backup_manager.cleanup()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class CompilationErrorParser:
|
|
254
|
+
"""编译错误解析器"""
|
|
255
|
+
|
|
256
|
+
# 各种语言的编译错误模式
|
|
257
|
+
ERROR_PATTERNS = {
|
|
258
|
+
'python': [
|
|
259
|
+
(r'File "([^"]+\.py)", line (\d+)', 'file_and_line'),
|
|
260
|
+
(r'SyntaxError.*in ([^\s]+\.py)', 'syntax_error'),
|
|
261
|
+
(r'ImportError.*from ([^\s]+\.py)', 'import_error'),
|
|
262
|
+
],
|
|
263
|
+
'java': [
|
|
264
|
+
(r'([^\s]+\.java):(\d+):', 'file_and_line'),
|
|
265
|
+
(r'error: cannot find symbol\s+Location: ([^\s]+\.java)', 'symbol_error'),
|
|
266
|
+
(r'error: package ([^\s]+) does not exist', 'package_error'),
|
|
267
|
+
],
|
|
268
|
+
'go': [
|
|
269
|
+
(r'# ([^\s]+)', 'package_error'),
|
|
270
|
+
(r'([^\s]+\.go):(\d+):(\d+):', 'file_and_line'),
|
|
271
|
+
(r'([^\s]+\.go):(\d+):', 'file_and_line'),
|
|
272
|
+
(r'undefined:\s+(\w+)', 'undefined_error'),
|
|
273
|
+
],
|
|
274
|
+
'c': [
|
|
275
|
+
(r'([^\s]+\.(c|h)):(\d+):(\d+):', 'file_and_line'),
|
|
276
|
+
(r'([^\s]+\.(c|h)):(\d+):', 'file_and_line'),
|
|
277
|
+
(r'undefined reference to', 'link_error'),
|
|
278
|
+
],
|
|
279
|
+
'cpp': [
|
|
280
|
+
(r'([^\s]+\.(cpp|hpp|cc|hh)):(\d+):(\d+):', 'file_and_line'),
|
|
281
|
+
(r'([^\s]+\.(cpp|hpp|cc|hh)):(\d+):', 'file_and_line'),
|
|
282
|
+
(r'undefined reference to', 'link_error'),
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@staticmethod
|
|
287
|
+
def parse_errors(errors: List[str], language: str, project_dir: str) -> Dict[str, List[Dict]]:
|
|
288
|
+
"""
|
|
289
|
+
解析编译错误
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
按文件分类的错误信息
|
|
293
|
+
"""
|
|
294
|
+
patterns = CompilationErrorParser.ERROR_PATTERNS.get(language, [])
|
|
295
|
+
file_errors: Dict[str, List[Dict]] = {}
|
|
296
|
+
|
|
297
|
+
for error in errors:
|
|
298
|
+
for pattern, error_type in patterns:
|
|
299
|
+
matches = re.findall(pattern, error)
|
|
300
|
+
for match in matches:
|
|
301
|
+
if isinstance(match, tuple):
|
|
302
|
+
file_path = match[0]
|
|
303
|
+
line_num = int(match[1]) if len(match) > 1 and match[1].isdigit() else 0
|
|
304
|
+
else:
|
|
305
|
+
file_path = match
|
|
306
|
+
line_num = 0
|
|
307
|
+
|
|
308
|
+
# 规范化路径
|
|
309
|
+
if not os.path.isabs(file_path):
|
|
310
|
+
file_path = os.path.join(project_dir, file_path)
|
|
311
|
+
file_path = os.path.normpath(file_path)
|
|
312
|
+
|
|
313
|
+
if file_path not in file_errors:
|
|
314
|
+
file_errors[file_path] = []
|
|
315
|
+
|
|
316
|
+
file_errors[file_path].append({
|
|
317
|
+
'line': line_num,
|
|
318
|
+
'type': error_type,
|
|
319
|
+
'message': error
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
return file_errors
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
环境检查器
|
|
4
|
+
检查各语言编译和测试环境是否满足要求
|
|
5
|
+
支持根据项目配置文件智能分析依赖
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import subprocess
|
|
9
|
+
import shutil
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import List, Dict, Optional, Tuple
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
class EnvironmentStatus(Enum):
|
|
18
|
+
OK = "ok"
|
|
19
|
+
MISSING = "missing"
|
|
20
|
+
WARNING = "warning"
|
|
21
|
+
OPTIONAL = "optional"
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class EnvironmentCheck:
|
|
25
|
+
"""环境检查项"""
|
|
26
|
+
name: str
|
|
27
|
+
status: EnvironmentStatus
|
|
28
|
+
version: Optional[str] = None
|
|
29
|
+
message: str = ""
|
|
30
|
+
suggestion: str = ""
|
|
31
|
+
required_by_project: bool = False
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class EnvironmentReport:
|
|
35
|
+
"""环境检查报告"""
|
|
36
|
+
language: str
|
|
37
|
+
checks: List[EnvironmentCheck] = field(default_factory=list)
|
|
38
|
+
all_passed: bool = True
|
|
39
|
+
critical_missing: List[str] = field(default_factory=list)
|
|
40
|
+
installation_suggestions: List[str] = field(default_factory=list)
|
|
41
|
+
project_config_detected: List[str] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
def add_check(self, check: EnvironmentCheck):
|
|
44
|
+
self.checks.append(check)
|
|
45
|
+
if check.status == EnvironmentStatus.MISSING and check.required_by_project:
|
|
46
|
+
self.all_passed = False
|
|
47
|
+
self.critical_missing.append(check.name)
|
|
48
|
+
|
|
49
|
+
class EnvironmentChecker:
|
|
50
|
+
"""环境检查器"""
|
|
51
|
+
|
|
52
|
+
def __init__(self):
|
|
53
|
+
self.check_results: Dict[str, EnvironmentReport] = {}
|
|
54
|
+
|
|
55
|
+
def check_language_environment(self, language: str, project_dir: str = None) -> EnvironmentReport:
|
|
56
|
+
"""检查特定语言的环境"""
|
|
57
|
+
report = EnvironmentReport(language=language)
|
|
58
|
+
|
|
59
|
+
# 分析项目依赖
|
|
60
|
+
if project_dir and os.path.exists(project_dir):
|
|
61
|
+
self._analyze_project_dependencies(report, language, project_dir)
|
|
62
|
+
|
|
63
|
+
if language == "python":
|
|
64
|
+
self._check_python_env(report)
|
|
65
|
+
elif language == "java":
|
|
66
|
+
self._check_java_env(report)
|
|
67
|
+
elif language == "go":
|
|
68
|
+
self._check_go_env(report)
|
|
69
|
+
elif language in ["c", "cpp"]:
|
|
70
|
+
self._check_c_cpp_env(report)
|
|
71
|
+
|
|
72
|
+
return report
|
|
73
|
+
|
|
74
|
+
def _analyze_project_dependencies(self, report: EnvironmentReport, language: str, project_dir: str):
|
|
75
|
+
"""分析项目配置文件"""
|
|
76
|
+
from coverage_tool.analyzers import DependencyAnalyzer
|
|
77
|
+
|
|
78
|
+
analyzer = DependencyAnalyzer(project_dir)
|
|
79
|
+
analysis = analyzer.analyze(language)
|
|
80
|
+
|
|
81
|
+
if analysis is None:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# 记录检测到的配置文件
|
|
85
|
+
if analysis.build_system:
|
|
86
|
+
report.project_config_detected.append(f"构建系统: {analysis.build_system}")
|
|
87
|
+
if analysis.test_framework:
|
|
88
|
+
report.project_config_detected.append(f"测试框架: {analysis.test_framework}")
|
|
89
|
+
if analysis.warnings:
|
|
90
|
+
for warning in analysis.warnings:
|
|
91
|
+
report.project_config_detected.append(f"⚠ {warning}")
|
|
92
|
+
|
|
93
|
+
# 检查项目依赖是否已安装
|
|
94
|
+
for dep in analysis.dependencies:
|
|
95
|
+
if dep.required or dep.installed:
|
|
96
|
+
check = EnvironmentCheck(
|
|
97
|
+
name=dep.name,
|
|
98
|
+
status=EnvironmentStatus.OK if dep.installed else EnvironmentStatus.MISSING,
|
|
99
|
+
version=dep.installed_version or dep.version or "",
|
|
100
|
+
message=f"{'已安装' if dep.installed else '未安装'}",
|
|
101
|
+
required_by_project=dep.required,
|
|
102
|
+
suggestion=self._get_suggestion(dep.name, language)
|
|
103
|
+
)
|
|
104
|
+
report.add_check(check)
|
|
105
|
+
|
|
106
|
+
def _get_suggestion(self, dep_name: str, language: str) -> str:
|
|
107
|
+
"""获取安装建议"""
|
|
108
|
+
suggestions = {
|
|
109
|
+
'pytest': 'pip install pytest',
|
|
110
|
+
'coverage': 'pip install coverage',
|
|
111
|
+
'tox': 'pip install tox',
|
|
112
|
+
'unittest': 'Python标准库,无需安装',
|
|
113
|
+
'maven': 'apt install maven (Linux) / brew install maven (macOS)',
|
|
114
|
+
'gradle': 'https://gradle.org/install/',
|
|
115
|
+
'java': 'https://adoptium.net/ 或 https://www.oracle.com/java/',
|
|
116
|
+
'go': 'https://go.dev/dl/',
|
|
117
|
+
'gcc': 'apt install gcc (Linux) / brew install gcc (macOS)',
|
|
118
|
+
'g++': 'apt install g++ (Linux) / brew install gcc (macOS)',
|
|
119
|
+
'make': 'apt install make (Linux) / brew install make (macOS)',
|
|
120
|
+
'cmake': 'https://cmake.org/download/',
|
|
121
|
+
'gcov': '随GCC安装',
|
|
122
|
+
'lcov': 'apt install lcov (Linux)',
|
|
123
|
+
'googletest': 'apt install libgtest-dev cmake',
|
|
124
|
+
}
|
|
125
|
+
return suggestions.get(dep_name, f'请安装 {dep_name}')
|
|
126
|
+
|
|
127
|
+
def _run_command(self, cmd: str) -> Tuple[bool, str, str]:
|
|
128
|
+
"""运行命令检查"""
|
|
129
|
+
try:
|
|
130
|
+
result = subprocess.run(
|
|
131
|
+
cmd,
|
|
132
|
+
shell=True,
|
|
133
|
+
capture_output=True,
|
|
134
|
+
text=True,
|
|
135
|
+
timeout=10
|
|
136
|
+
)
|
|
137
|
+
return result.returncode == 0, result.stdout, result.stderr
|
|
138
|
+
except subprocess.TimeoutExpired:
|
|
139
|
+
return False, "", "命令执行超时"
|
|
140
|
+
except Exception as e:
|
|
141
|
+
return False, "", str(e)
|
|
142
|
+
|
|
143
|
+
def _check_python_env(self, report: EnvironmentReport):
|
|
144
|
+
"""检查Python环境"""
|
|
145
|
+
check = EnvironmentCheck(
|
|
146
|
+
name="Python",
|
|
147
|
+
status=EnvironmentStatus.OK,
|
|
148
|
+
version="",
|
|
149
|
+
message="Python 环境正常",
|
|
150
|
+
required_by_project=True
|
|
151
|
+
)
|
|
152
|
+
success, stdout, _ = self._run_command("python3 --version")
|
|
153
|
+
if success:
|
|
154
|
+
check.version = stdout.strip()
|
|
155
|
+
check.message = f"Python 版本: {check.version}"
|
|
156
|
+
else:
|
|
157
|
+
check.status = EnvironmentStatus.MISSING
|
|
158
|
+
check.message = "未找到 Python"
|
|
159
|
+
check.suggestion = "请安装 Python 3.7+: https://www.python.org/downloads/"
|
|
160
|
+
report.add_check(check)
|
|
161
|
+
|
|
162
|
+
# 检查pytest
|
|
163
|
+
success, stdout, _ = self._run_command("python3 -m pytest --version")
|
|
164
|
+
check = EnvironmentCheck(
|
|
165
|
+
name="pytest",
|
|
166
|
+
status=EnvironmentStatus.OK if success else EnvironmentStatus.WARNING,
|
|
167
|
+
message="pytest 未安装" if not success else f"pytest 版本: {stdout.strip().split(chr(10))[0] if stdout else '未知'}",
|
|
168
|
+
suggestion="pip install pytest" if not success else "",
|
|
169
|
+
required_by_project=True
|
|
170
|
+
)
|
|
171
|
+
if success:
|
|
172
|
+
check.version = stdout.strip().split('\n')[0]
|
|
173
|
+
report.add_check(check)
|
|
174
|
+
|
|
175
|
+
# 检查coverage
|
|
176
|
+
success, stdout, _ = self._run_command("python3 -m coverage --version")
|
|
177
|
+
check = EnvironmentCheck(
|
|
178
|
+
name="coverage.py",
|
|
179
|
+
status=EnvironmentStatus.OK if success else EnvironmentStatus.WARNING,
|
|
180
|
+
message="coverage.py 未安装" if not success else f"coverage.py 已安装",
|
|
181
|
+
suggestion="pip install coverage" if not success else "",
|
|
182
|
+
required_by_project=False
|
|
183
|
+
)
|
|
184
|
+
if success:
|
|
185
|
+
check.version = stdout.strip().split('\n')[0]
|
|
186
|
+
report.add_check(check)
|
|
187
|
+
|
|
188
|
+
def _check_java_env(self, report: EnvironmentReport):
|
|
189
|
+
"""检查Java环境"""
|
|
190
|
+
success, stdout, stderr = self._run_command("java -version 2>&1")
|
|
191
|
+
check = EnvironmentCheck(
|
|
192
|
+
name="Java JDK",
|
|
193
|
+
status=EnvironmentStatus.OK if success else EnvironmentStatus.MISSING,
|
|
194
|
+
version="",
|
|
195
|
+
message="未找到 Java JDK" if not success else f"Java 版本: {(stdout or stderr).strip().split(chr(10))[0] if (stdout or stderr) else '未知'}",
|
|
196
|
+
suggestion="请安装 JDK 8+: https://adoptium.net/" if not success else "",
|
|
197
|
+
required_by_project=True
|
|
198
|
+
)
|
|
199
|
+
if success:
|
|
200
|
+
check.version = (stdout or stderr).strip().split('\n')[0]
|
|
201
|
+
report.add_check(check)
|
|
202
|
+
|
|
203
|
+
success, stdout, _ = self._run_command("mvn -version")
|
|
204
|
+
check = EnvironmentCheck(
|
|
205
|
+
name="Maven",
|
|
206
|
+
status=EnvironmentStatus.OK if success else EnvironmentStatus.WARNING,
|
|
207
|
+
message="Maven 未安装" if not success else f"Maven 版本: {stdout.strip().split(chr(10))[0] if stdout else '未知'}",
|
|
208
|
+
suggestion="apt install maven / brew install maven" if not success else "",
|
|
209
|
+
required_by_project=False
|
|
210
|
+
)
|
|
211
|
+
if success:
|
|
212
|
+
check.version = stdout.strip().split('\n')[0]
|
|
213
|
+
report.add_check(check)
|
|
214
|
+
|
|
215
|
+
def _check_go_env(self, report: EnvironmentReport):
|
|
216
|
+
"""检查Go环境"""
|
|
217
|
+
success, stdout, _ = self._run_command("go version")
|
|
218
|
+
check = EnvironmentCheck(
|
|
219
|
+
name="Go SDK",
|
|
220
|
+
status=EnvironmentStatus.OK if success else EnvironmentStatus.MISSING,
|
|
221
|
+
version="",
|
|
222
|
+
message="未找到 Go SDK" if not success else f"Go 版本: {stdout.strip() if stdout else '未知'}",
|
|
223
|
+
suggestion="请安装 Go 1.16+: https://go.dev/dl/" if not success else "",
|
|
224
|
+
required_by_project=True
|
|
225
|
+
)
|
|
226
|
+
if success:
|
|
227
|
+
check.version = stdout.strip()
|
|
228
|
+
report.add_check(check)
|
|
229
|
+
|
|
230
|
+
def _check_c_cpp_env(self, report: EnvironmentReport):
|
|
231
|
+
"""检查C/C++环境"""
|
|
232
|
+
success, stdout, _ = self._run_command("gcc --version")
|
|
233
|
+
check = EnvironmentCheck(
|
|
234
|
+
name="GCC",
|
|
235
|
+
status=EnvironmentStatus.OK if success else EnvironmentStatus.MISSING,
|
|
236
|
+
version="",
|
|
237
|
+
message="GCC 未安装" if not success else f"GCC 版本: {stdout.strip().split(chr(10))[0] if stdout else '未知'}",
|
|
238
|
+
suggestion="apt install gcc (Linux) / brew install gcc (macOS)" if not success else "",
|
|
239
|
+
required_by_project=True
|
|
240
|
+
)
|
|
241
|
+
if success:
|
|
242
|
+
check.version = stdout.strip().split('\n')[0]
|
|
243
|
+
report.add_check(check)
|
|
244
|
+
|
|
245
|
+
success, stdout, _ = self._run_command("g++ --version")
|
|
246
|
+
check = EnvironmentCheck(
|
|
247
|
+
name="G++",
|
|
248
|
+
status=EnvironmentStatus.OK if success else EnvironmentStatus.MISSING,
|
|
249
|
+
version="",
|
|
250
|
+
message="G++ 未安装" if not success else f"G++ 版本: {stdout.strip().split(chr(10))[0] if stdout else '未知'}",
|
|
251
|
+
suggestion="apt install g++ (Linux) / brew install gcc (macOS)" if not success else "",
|
|
252
|
+
required_by_project=True
|
|
253
|
+
)
|
|
254
|
+
if success:
|
|
255
|
+
check.version = stdout.strip().split('\n')[0]
|
|
256
|
+
report.add_check(check)
|
|
257
|
+
|
|
258
|
+
success, stdout, _ = self._run_command("make --version")
|
|
259
|
+
check = EnvironmentCheck(
|
|
260
|
+
name="Make",
|
|
261
|
+
status=EnvironmentStatus.OK if success else EnvironmentStatus.MISSING,
|
|
262
|
+
version="",
|
|
263
|
+
message="Make 未安装" if not success else f"Make 版本: {stdout.strip().split(chr(10))[0] if stdout else '未知'}",
|
|
264
|
+
suggestion="apt install make (Linux) / brew install make (macOS)" if not success else "",
|
|
265
|
+
required_by_project=True
|
|
266
|
+
)
|
|
267
|
+
if success:
|
|
268
|
+
check.version = stdout.strip().split('\n')[0]
|
|
269
|
+
report.add_check(check)
|
|
270
|
+
|
|
271
|
+
def print_report(self, report: EnvironmentReport):
|
|
272
|
+
"""打印环境检查报告"""
|
|
273
|
+
print(f"\n{'='*60}")
|
|
274
|
+
print(f"环境检查报告: {report.language}")
|
|
275
|
+
print('='*60)
|
|
276
|
+
|
|
277
|
+
# 显示检测到的项目配置
|
|
278
|
+
if report.project_config_detected:
|
|
279
|
+
print("\n📁 检测到的项目配置:")
|
|
280
|
+
for config in report.project_config_detected:
|
|
281
|
+
print(f" • {config}")
|
|
282
|
+
|
|
283
|
+
for check in report.checks:
|
|
284
|
+
if check.status == EnvironmentStatus.OK:
|
|
285
|
+
status_icon = "✓"
|
|
286
|
+
elif check.status == EnvironmentStatus.WARNING:
|
|
287
|
+
status_icon = "⚠"
|
|
288
|
+
elif check.status == EnvironmentStatus.MISSING:
|
|
289
|
+
status_icon = "✗"
|
|
290
|
+
else:
|
|
291
|
+
status_icon = "○"
|
|
292
|
+
|
|
293
|
+
print(f"\n{status_icon} {check.name}")
|
|
294
|
+
if check.version:
|
|
295
|
+
print(f" 版本: {check.version}")
|
|
296
|
+
print(f" {check.message}")
|
|
297
|
+
if check.suggestion:
|
|
298
|
+
print(f" 安装: {check.suggestion}")
|
|
299
|
+
|
|
300
|
+
print(f"\n{'='*60}")
|
|
301
|
+
if report.all_passed:
|
|
302
|
+
print("✓ 环境检查通过")
|
|
303
|
+
else:
|
|
304
|
+
print("✗ 存在缺失的关键依赖,请先安装")
|
|
305
|
+
print(f"缺失项: {', '.join(report.critical_missing)}")
|
|
306
|
+
print('='*60)
|
|
307
|
+
|
|
308
|
+
def check_all_and_print(self, language: str, project_dir: str = None):
|
|
309
|
+
"""检查并打印所有环境信息"""
|
|
310
|
+
report = self.check_language_environment(language, project_dir)
|
|
311
|
+
self.print_report(report)
|
|
312
|
+
return report
|