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,210 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
C测试删除器
|
|
4
|
+
使用正则表达式解析C测试文件
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import List, Optional, Tuple
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from coverage_tool.test_removers.base import BaseSmartTestRemover, TestFunction
|
|
12
|
+
from coverage_tool.parsers import TestCase
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CSmartTestRemover(BaseSmartTestRemover):
|
|
16
|
+
"""C智能测试删除器"""
|
|
17
|
+
|
|
18
|
+
# C语言测试文件匹配模式
|
|
19
|
+
C_TEST_PATTERNS = [
|
|
20
|
+
"test_*.c",
|
|
21
|
+
"*_test.c",
|
|
22
|
+
"*test*.c",
|
|
23
|
+
"check_*.c",
|
|
24
|
+
"tests/*.c",
|
|
25
|
+
"*_tests.c",
|
|
26
|
+
"tests_*.c",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# 预编译正则表达式
|
|
30
|
+
TEST_FUNC_PATTERNS = [
|
|
31
|
+
re.compile(r'^(void|int|bool)\s+(test_\w+)\s*\([^)]*\)\s*\{', re.MULTILINE),
|
|
32
|
+
re.compile(r'^(void|int|bool)\s+(check_\w+)\s*\([^)]*\)\s*\{', re.MULTILINE),
|
|
33
|
+
re.compile(r'^(void|int|bool)\s+(\w+_test)\s*\([^)]*\)\s*\{', re.MULTILINE),
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
def _is_build_failure(self, test_name: str) -> bool:
|
|
37
|
+
"""检查是否是C构建失败的特殊测试名称"""
|
|
38
|
+
build_failure_patterns = [
|
|
39
|
+
'[build failed]',
|
|
40
|
+
'[compilation failed]',
|
|
41
|
+
'error:',
|
|
42
|
+
'undefined reference',
|
|
43
|
+
'linker error',
|
|
44
|
+
'compilation error',
|
|
45
|
+
'syntax error',
|
|
46
|
+
]
|
|
47
|
+
return any(pattern in test_name for pattern in build_failure_patterns)
|
|
48
|
+
|
|
49
|
+
def remove_all_tests_in_package(self, package_path: str) -> Tuple[int, List[str]]:
|
|
50
|
+
"""
|
|
51
|
+
删除指定包/目录内的所有测试文件(C使用目录方式)
|
|
52
|
+
用于处理目录级编译错误
|
|
53
|
+
"""
|
|
54
|
+
return self.remove_all_tests_in_directory(package_path)
|
|
55
|
+
|
|
56
|
+
def remove_all_tests_in_directory(self, directory_path: str) -> Tuple[int, List[str]]:
|
|
57
|
+
"""
|
|
58
|
+
删除指定目录内的所有测试文件
|
|
59
|
+
用于处理C/C++目录级编译错误
|
|
60
|
+
"""
|
|
61
|
+
deleted_count = 0
|
|
62
|
+
deleted_files = []
|
|
63
|
+
|
|
64
|
+
# 按完整路径搜索
|
|
65
|
+
dir_path = Path(self.project_dir) / directory_path
|
|
66
|
+
|
|
67
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
68
|
+
test_files = []
|
|
69
|
+
for pattern in self.C_TEST_PATTERNS:
|
|
70
|
+
test_files.extend(dir_path.glob(pattern))
|
|
71
|
+
# 去重
|
|
72
|
+
test_files = list(set(test_files))
|
|
73
|
+
|
|
74
|
+
if test_files:
|
|
75
|
+
self.logger.info(f"[编译错误] 在目录 {dir_path} 发现 {len(test_files)} 个C测试文件")
|
|
76
|
+
|
|
77
|
+
for test_file in test_files:
|
|
78
|
+
try:
|
|
79
|
+
# 备份文件
|
|
80
|
+
self.backup_manager.backup_file(str(test_file))
|
|
81
|
+
# 删除文件
|
|
82
|
+
test_file.unlink()
|
|
83
|
+
deleted_count += 1
|
|
84
|
+
deleted_files.append(str(test_file))
|
|
85
|
+
self.logger.info(f"[编译错误] ✓ 已删除C测试文件: {test_file}")
|
|
86
|
+
except Exception as e:
|
|
87
|
+
self.logger.error(f"[编译错误] 删除文件失败: {test_file}: {e}")
|
|
88
|
+
|
|
89
|
+
return deleted_count, deleted_files
|
|
90
|
+
|
|
91
|
+
# 如果没找到,尝试将classname作为子目录搜索
|
|
92
|
+
parts = directory_path.replace('.', '/').split('/')
|
|
93
|
+
for i in range(len(parts)):
|
|
94
|
+
subdir = '/'.join(parts[i:])
|
|
95
|
+
search_dir = Path(self.project_dir) / subdir
|
|
96
|
+
if search_dir.exists() and search_dir.is_dir():
|
|
97
|
+
test_files = []
|
|
98
|
+
for pattern in self.C_TEST_PATTERNS:
|
|
99
|
+
test_files.extend(search_dir.glob(pattern))
|
|
100
|
+
test_files = list(set(test_files))
|
|
101
|
+
|
|
102
|
+
if test_files:
|
|
103
|
+
self.logger.info(f"[编译错误] 在目录 {search_dir} 发现 {len(test_files)} 个C测试文件")
|
|
104
|
+
|
|
105
|
+
for test_file in test_files:
|
|
106
|
+
try:
|
|
107
|
+
self.backup_manager.backup_file(str(test_file))
|
|
108
|
+
test_file.unlink()
|
|
109
|
+
deleted_count += 1
|
|
110
|
+
deleted_files.append(str(test_file))
|
|
111
|
+
self.logger.info(f"[编译错误] ✓ 已删除C测试文件: {test_file}")
|
|
112
|
+
except Exception as e:
|
|
113
|
+
self.logger.error(f"[编译错误] 删除文件失败: {test_file}: {e}")
|
|
114
|
+
|
|
115
|
+
return deleted_count, deleted_files
|
|
116
|
+
|
|
117
|
+
return deleted_count, deleted_files
|
|
118
|
+
|
|
119
|
+
def find_test_functions(self, file_path: str) -> List[TestFunction]:
|
|
120
|
+
"""查找C测试函数"""
|
|
121
|
+
test_functions = []
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
125
|
+
content = f.read()
|
|
126
|
+
|
|
127
|
+
for pattern in self.TEST_FUNC_PATTERNS:
|
|
128
|
+
for match in pattern.finditer(content):
|
|
129
|
+
func_name = match.group(2)
|
|
130
|
+
start_pos = match.start()
|
|
131
|
+
start_line = content[:start_pos].count('\n') + 1
|
|
132
|
+
|
|
133
|
+
# 找到函数结束
|
|
134
|
+
brace_count = 0
|
|
135
|
+
end_pos = match.end()
|
|
136
|
+
for i, char in enumerate(content[match.end():]):
|
|
137
|
+
if char == '{':
|
|
138
|
+
brace_count += 1
|
|
139
|
+
elif char == '}':
|
|
140
|
+
if brace_count == 0:
|
|
141
|
+
end_pos = match.end() + i + 1
|
|
142
|
+
break
|
|
143
|
+
brace_count -= 1
|
|
144
|
+
|
|
145
|
+
end_line = content[:end_pos].count('\n') + 1
|
|
146
|
+
|
|
147
|
+
test_func = TestFunction(
|
|
148
|
+
name=func_name,
|
|
149
|
+
file_path=file_path,
|
|
150
|
+
start_line=start_line,
|
|
151
|
+
end_line=end_line
|
|
152
|
+
)
|
|
153
|
+
test_functions.append(test_func)
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
self.logger.error(f"解析C文件失败 {file_path}: {e}")
|
|
157
|
+
|
|
158
|
+
return test_functions
|
|
159
|
+
|
|
160
|
+
def find_test_file(self, test_case: TestCase) -> Optional[str]:
|
|
161
|
+
"""查找C测试文件"""
|
|
162
|
+
classname = test_case.classname
|
|
163
|
+
test_name = test_case.name
|
|
164
|
+
|
|
165
|
+
# 处理特殊的构建失败错误
|
|
166
|
+
if self._is_build_failure(test_name):
|
|
167
|
+
self.logger.info(f"[编译错误] 检测到C编译失败: {test_name}")
|
|
168
|
+
# 尝试从classname推断目录
|
|
169
|
+
search_dirs = [classname]
|
|
170
|
+
|
|
171
|
+
# 也尝试将点分隔的包名转换为路径
|
|
172
|
+
if '.' in classname:
|
|
173
|
+
search_dirs.append(classname.replace('.', '/'))
|
|
174
|
+
|
|
175
|
+
for search_dir in search_dirs:
|
|
176
|
+
# 尝试不同的目录组合
|
|
177
|
+
parts = search_dir.replace('.', '/').split('/')
|
|
178
|
+
for i in range(len(parts)):
|
|
179
|
+
subdir = '/'.join(parts[i:])
|
|
180
|
+
test_dir = Path(self.project_dir) / subdir
|
|
181
|
+
|
|
182
|
+
if test_dir.exists() and test_dir.is_dir():
|
|
183
|
+
# 查找该目录下的测试文件
|
|
184
|
+
for pattern in self.C_TEST_PATTERNS:
|
|
185
|
+
for test_file in test_dir.glob(pattern):
|
|
186
|
+
if test_file.exists():
|
|
187
|
+
self.logger.info(f"[编译错误] 找到目录 {search_dir} 的C测试文件: {test_file}")
|
|
188
|
+
return str(test_file)
|
|
189
|
+
|
|
190
|
+
# 标准测试文件查找
|
|
191
|
+
patterns = [
|
|
192
|
+
f"{classname}_test.c",
|
|
193
|
+
f"test_{classname}.c",
|
|
194
|
+
f"tests/{classname}_test.c",
|
|
195
|
+
f"check_{classname}.c",
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
for pattern in patterns:
|
|
199
|
+
file_path = Path(self.project_dir) / pattern
|
|
200
|
+
if file_path.exists():
|
|
201
|
+
return str(file_path)
|
|
202
|
+
|
|
203
|
+
# 使用glob查找
|
|
204
|
+
for pattern in self.C_TEST_PATTERNS:
|
|
205
|
+
if '*' in pattern:
|
|
206
|
+
for file_path in Path(self.project_dir).rglob(pattern):
|
|
207
|
+
if file_path.exists():
|
|
208
|
+
return str(file_path)
|
|
209
|
+
|
|
210
|
+
return None
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
C++测试删除器
|
|
4
|
+
支持Google Test和普通C++测试
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import List, Optional, Tuple
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from coverage_tool.test_removers.base import BaseSmartTestRemover, TestFunction
|
|
12
|
+
from coverage_tool.parsers import TestCase
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CPPSmartTestRemover(BaseSmartTestRemover):
|
|
16
|
+
"""C++智能测试删除器 - 支持Google Test"""
|
|
17
|
+
|
|
18
|
+
# C++语言测试文件匹配模式
|
|
19
|
+
CPP_TEST_PATTERNS = [
|
|
20
|
+
"test_*.cpp",
|
|
21
|
+
"*_test.cpp",
|
|
22
|
+
"*test*.cpp",
|
|
23
|
+
"Test*.cpp",
|
|
24
|
+
"*.test.cpp",
|
|
25
|
+
"test_*.cc",
|
|
26
|
+
"*_test.cc",
|
|
27
|
+
"*test*.cc",
|
|
28
|
+
"Test*.cc",
|
|
29
|
+
"*.test.cc",
|
|
30
|
+
"tests/*.cpp",
|
|
31
|
+
"tests/*.cc",
|
|
32
|
+
"*_tests.cpp",
|
|
33
|
+
"*_tests.cc",
|
|
34
|
+
"tests_*.cpp",
|
|
35
|
+
"tests_*.cc",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# 预编译正则表达式
|
|
39
|
+
GTEST_PATTERN = re.compile(
|
|
40
|
+
r'TEST\s*\(\s*(\w+)\s*,\s*(\w+)\s*\)\s*\{',
|
|
41
|
+
re.MULTILINE
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
FUNC_PATTERN = re.compile(
|
|
45
|
+
r'^(void|int|bool)\s+(test_\w+)\s*\([^)]*\)\s*\{',
|
|
46
|
+
re.MULTILINE
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def _is_build_failure(self, test_name: str) -> bool:
|
|
50
|
+
"""检查是否是C++构建失败的特殊测试名称"""
|
|
51
|
+
build_failure_patterns = [
|
|
52
|
+
'[build failed]',
|
|
53
|
+
'[compilation failed]',
|
|
54
|
+
'error:',
|
|
55
|
+
'undefined reference',
|
|
56
|
+
'linker error',
|
|
57
|
+
'compilation error',
|
|
58
|
+
'syntax error',
|
|
59
|
+
]
|
|
60
|
+
return any(pattern in test_name for pattern in build_failure_patterns)
|
|
61
|
+
|
|
62
|
+
def remove_all_tests_in_package(self, package_path: str) -> Tuple[int, List[str]]:
|
|
63
|
+
"""
|
|
64
|
+
删除指定包/目录内的所有测试文件(C++使用目录方式)
|
|
65
|
+
用于处理目录级编译错误
|
|
66
|
+
"""
|
|
67
|
+
return self.remove_all_tests_in_directory(package_path)
|
|
68
|
+
|
|
69
|
+
def remove_all_tests_in_directory(self, directory_path: str) -> Tuple[int, List[str]]:
|
|
70
|
+
"""
|
|
71
|
+
删除指定目录内的所有测试文件
|
|
72
|
+
用于处理C/C++目录级编译错误
|
|
73
|
+
"""
|
|
74
|
+
deleted_count = 0
|
|
75
|
+
deleted_files = []
|
|
76
|
+
|
|
77
|
+
# 按完整路径搜索
|
|
78
|
+
dir_path = Path(self.project_dir) / directory_path
|
|
79
|
+
|
|
80
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
81
|
+
test_files = []
|
|
82
|
+
for pattern in self.CPP_TEST_PATTERNS:
|
|
83
|
+
test_files.extend(dir_path.glob(pattern))
|
|
84
|
+
# 去重
|
|
85
|
+
test_files = list(set(test_files))
|
|
86
|
+
|
|
87
|
+
if test_files:
|
|
88
|
+
self.logger.info(f"[编译错误] 在目录 {dir_path} 发现 {len(test_files)} 个C++测试文件")
|
|
89
|
+
|
|
90
|
+
for test_file in test_files:
|
|
91
|
+
try:
|
|
92
|
+
# 备份文件
|
|
93
|
+
self.backup_manager.backup_file(str(test_file))
|
|
94
|
+
# 删除文件
|
|
95
|
+
test_file.unlink()
|
|
96
|
+
deleted_count += 1
|
|
97
|
+
deleted_files.append(str(test_file))
|
|
98
|
+
self.logger.info(f"[编译错误] ✓ 已删除C++测试文件: {test_file}")
|
|
99
|
+
except Exception as e:
|
|
100
|
+
self.logger.error(f"[编译错误] 删除文件失败: {test_file}: {e}")
|
|
101
|
+
|
|
102
|
+
return deleted_count, deleted_files
|
|
103
|
+
|
|
104
|
+
# 如果没找到,尝试将classname作为子目录搜索
|
|
105
|
+
parts = directory_path.replace('.', '/').split('/')
|
|
106
|
+
for i in range(len(parts)):
|
|
107
|
+
subdir = '/'.join(parts[i:])
|
|
108
|
+
search_dir = Path(self.project_dir) / subdir
|
|
109
|
+
if search_dir.exists() and search_dir.is_dir():
|
|
110
|
+
test_files = []
|
|
111
|
+
for pattern in self.CPP_TEST_PATTERNS:
|
|
112
|
+
test_files.extend(search_dir.glob(pattern))
|
|
113
|
+
test_files = list(set(test_files))
|
|
114
|
+
|
|
115
|
+
if test_files:
|
|
116
|
+
self.logger.info(f"[编译错误] 在目录 {search_dir} 发现 {len(test_files)} 个C++测试文件")
|
|
117
|
+
|
|
118
|
+
for test_file in test_files:
|
|
119
|
+
try:
|
|
120
|
+
self.backup_manager.backup_file(str(test_file))
|
|
121
|
+
test_file.unlink()
|
|
122
|
+
deleted_count += 1
|
|
123
|
+
deleted_files.append(str(test_file))
|
|
124
|
+
self.logger.info(f"[编译错误] ✓ 已删除C++测试文件: {test_file}")
|
|
125
|
+
except Exception as e:
|
|
126
|
+
self.logger.error(f"[编译错误] 删除文件失败: {test_file}: {e}")
|
|
127
|
+
|
|
128
|
+
return deleted_count, deleted_files
|
|
129
|
+
|
|
130
|
+
return deleted_count, deleted_files
|
|
131
|
+
|
|
132
|
+
def find_test_functions(self, file_path: str) -> List[TestFunction]:
|
|
133
|
+
"""查找C++测试函数(Google Test格式)"""
|
|
134
|
+
test_functions = []
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
138
|
+
content = f.read()
|
|
139
|
+
|
|
140
|
+
# 匹配Google Test的TEST宏
|
|
141
|
+
for match in self.GTEST_PATTERN.finditer(content):
|
|
142
|
+
suite_name = match.group(1)
|
|
143
|
+
test_name = match.group(2)
|
|
144
|
+
full_name = f"{suite_name}.{test_name}"
|
|
145
|
+
|
|
146
|
+
start_pos = match.start()
|
|
147
|
+
start_line = content[:start_pos].count('\n') + 1
|
|
148
|
+
|
|
149
|
+
# 找到测试结束
|
|
150
|
+
brace_count = 0
|
|
151
|
+
end_pos = match.end()
|
|
152
|
+
for i, char in enumerate(content[match.end():]):
|
|
153
|
+
if char == '{':
|
|
154
|
+
brace_count += 1
|
|
155
|
+
elif char == '}':
|
|
156
|
+
if brace_count == 0:
|
|
157
|
+
end_pos = match.end() + i + 1
|
|
158
|
+
break
|
|
159
|
+
brace_count -= 1
|
|
160
|
+
|
|
161
|
+
end_line = content[:end_pos].count('\n') + 1
|
|
162
|
+
|
|
163
|
+
test_func = TestFunction(
|
|
164
|
+
name=full_name,
|
|
165
|
+
file_path=file_path,
|
|
166
|
+
start_line=start_line,
|
|
167
|
+
end_line=end_line
|
|
168
|
+
)
|
|
169
|
+
test_functions.append(test_func)
|
|
170
|
+
|
|
171
|
+
# 也匹配普通测试函数
|
|
172
|
+
for match in self.FUNC_PATTERN.finditer(content):
|
|
173
|
+
func_name = match.group(2)
|
|
174
|
+
start_pos = match.start()
|
|
175
|
+
start_line = content[:start_pos].count('\n') + 1
|
|
176
|
+
|
|
177
|
+
brace_count = 0
|
|
178
|
+
end_pos = match.end()
|
|
179
|
+
for i, char in enumerate(content[match.end():]):
|
|
180
|
+
if char == '{':
|
|
181
|
+
brace_count += 1
|
|
182
|
+
elif char == '}':
|
|
183
|
+
if brace_count == 0:
|
|
184
|
+
end_pos = match.end() + i + 1
|
|
185
|
+
break
|
|
186
|
+
brace_count -= 1
|
|
187
|
+
|
|
188
|
+
end_line = content[:end_pos].count('\n') + 1
|
|
189
|
+
|
|
190
|
+
test_func = TestFunction(
|
|
191
|
+
name=func_name,
|
|
192
|
+
file_path=file_path,
|
|
193
|
+
start_line=start_line,
|
|
194
|
+
end_line=end_line
|
|
195
|
+
)
|
|
196
|
+
test_functions.append(test_func)
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
self.logger.error(f"解析C++文件失败 {file_path}: {e}")
|
|
200
|
+
|
|
201
|
+
return test_functions
|
|
202
|
+
|
|
203
|
+
def find_test_file(self, test_case: TestCase) -> Optional[str]:
|
|
204
|
+
"""查找C++测试文件"""
|
|
205
|
+
classname = test_case.classname
|
|
206
|
+
test_name = test_case.name
|
|
207
|
+
|
|
208
|
+
# 处理特殊的构建失败错误
|
|
209
|
+
if self._is_build_failure(test_name):
|
|
210
|
+
self.logger.info(f"[编译错误] 检测到C++编译失败: {test_name}")
|
|
211
|
+
# 尝试从classname推断目录
|
|
212
|
+
search_dirs = [classname]
|
|
213
|
+
|
|
214
|
+
# 也尝试将点分隔的包名转换为路径
|
|
215
|
+
if '.' in classname:
|
|
216
|
+
search_dirs.append(classname.replace('.', '/'))
|
|
217
|
+
|
|
218
|
+
for search_dir in search_dirs:
|
|
219
|
+
# 尝试不同的目录组合
|
|
220
|
+
parts = search_dir.replace('.', '/').split('/')
|
|
221
|
+
for i in range(len(parts)):
|
|
222
|
+
subdir = '/'.join(parts[i:])
|
|
223
|
+
test_dir = Path(self.project_dir) / subdir
|
|
224
|
+
|
|
225
|
+
if test_dir.exists() and test_dir.is_dir():
|
|
226
|
+
# 查找该目录下的测试文件
|
|
227
|
+
for pattern in self.CPP_TEST_PATTERNS:
|
|
228
|
+
for test_file in test_dir.glob(pattern):
|
|
229
|
+
if test_file.exists():
|
|
230
|
+
self.logger.info(f"[编译错误] 找到目录 {search_dir} 的C++测试文件: {test_file}")
|
|
231
|
+
return str(test_file)
|
|
232
|
+
|
|
233
|
+
# 处理 Google Test 的 suite.name 格式
|
|
234
|
+
if '.' in test_name:
|
|
235
|
+
suite_name = test_name.split('.')[0]
|
|
236
|
+
patterns = [
|
|
237
|
+
f"{classname}_test.cpp",
|
|
238
|
+
f"test_{classname}.cpp",
|
|
239
|
+
f"tests/{classname}_test.cpp",
|
|
240
|
+
f"Test{classname}.cpp",
|
|
241
|
+
f"{classname}.test.cpp",
|
|
242
|
+
f"{classname}_test.cc",
|
|
243
|
+
f"test_{classname}.cc",
|
|
244
|
+
f"Test{classname}.cc",
|
|
245
|
+
f"{classname}.test.cc",
|
|
246
|
+
]
|
|
247
|
+
else:
|
|
248
|
+
patterns = [
|
|
249
|
+
f"{classname}_test.cpp",
|
|
250
|
+
f"test_{classname}.cpp",
|
|
251
|
+
f"tests/{classname}_test.cpp",
|
|
252
|
+
f"Test{classname}.cpp",
|
|
253
|
+
f"{classname}.test.cpp",
|
|
254
|
+
f"{classname}_test.cc",
|
|
255
|
+
f"test_{classname}.cc",
|
|
256
|
+
f"Test{classname}.cc",
|
|
257
|
+
f"{classname}.test.cc",
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
for pattern in patterns:
|
|
261
|
+
file_path = Path(self.project_dir) / pattern
|
|
262
|
+
if file_path.exists():
|
|
263
|
+
return str(file_path)
|
|
264
|
+
|
|
265
|
+
# 使用glob查找
|
|
266
|
+
for pattern in self.CPP_TEST_PATTERNS:
|
|
267
|
+
if '*' in pattern:
|
|
268
|
+
for file_path in Path(self.project_dir).rglob(pattern):
|
|
269
|
+
if file_path.exists():
|
|
270
|
+
return str(file_path)
|
|
271
|
+
|
|
272
|
+
return None
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
测试删除器工厂
|
|
4
|
+
提供统一的接口来获取不同语言的测试删除器
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from coverage_tool.utils import Logger
|
|
10
|
+
from coverage_tool.test_removers.base import BaseSmartTestRemover, TestFunction, TestRemovalStrategy
|
|
11
|
+
from coverage_tool.test_removers.python_remover import PythonSmartTestRemover
|
|
12
|
+
from coverage_tool.test_removers.java_remover import JavaSmartTestRemover
|
|
13
|
+
from coverage_tool.test_removers.go_remover import GoSmartTestRemover
|
|
14
|
+
from coverage_tool.test_removers.c_remover import CSmartTestRemover
|
|
15
|
+
from coverage_tool.test_removers.cpp_remover import CPPSmartTestRemover
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SmartTestRemoverFactory:
|
|
19
|
+
"""智能测试删除器工厂"""
|
|
20
|
+
|
|
21
|
+
_removers = {
|
|
22
|
+
'python': PythonSmartTestRemover,
|
|
23
|
+
'java': JavaSmartTestRemover,
|
|
24
|
+
'go': GoSmartTestRemover,
|
|
25
|
+
'c': CSmartTestRemover,
|
|
26
|
+
'cpp': CPPSmartTestRemover,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def get_remover(cls, language: str, project_dir: str, logger: Optional[Logger] = None) -> BaseSmartTestRemover:
|
|
31
|
+
"""获取对应语言的测试删除器"""
|
|
32
|
+
remover_class = cls._removers.get(language.lower())
|
|
33
|
+
if remover_class is None:
|
|
34
|
+
raise ValueError(f"不支持的语言: {language}")
|
|
35
|
+
return remover_class(project_dir, logger)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def register_remover(cls, language: str, remover_class: type):
|
|
39
|
+
"""注册新的测试删除器"""
|
|
40
|
+
cls._removers[language.lower()] = remover_class
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def supported_languages(cls) -> list:
|
|
44
|
+
"""返回支持的语言列表"""
|
|
45
|
+
return list(cls._removers.keys())
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Go测试删除器
|
|
4
|
+
使用正则表达式解析Go测试文件
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from coverage_tool.test_removers.base import BaseSmartTestRemover, TestFunction
|
|
12
|
+
from coverage_tool.parsers import TestCase
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GoSmartTestRemover(BaseSmartTestRemover):
|
|
16
|
+
"""Go智能测试删除器"""
|
|
17
|
+
|
|
18
|
+
# 预编译正则表达式
|
|
19
|
+
TEST_FUNC_PATTERN = re.compile(
|
|
20
|
+
r'^func\s+(Test\w+)\s*\([^)]*\*testing\.T[^)]*\)\s*\{',
|
|
21
|
+
re.MULTILINE
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def find_test_functions(self, file_path: str) -> List[TestFunction]:
|
|
25
|
+
"""查找Go测试函数"""
|
|
26
|
+
test_functions = []
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
30
|
+
content = f.read()
|
|
31
|
+
|
|
32
|
+
for match in self.TEST_FUNC_PATTERN.finditer(content):
|
|
33
|
+
func_name = match.group(1)
|
|
34
|
+
start_pos = match.start()
|
|
35
|
+
start_line = content[:start_pos].count('\n') + 1
|
|
36
|
+
|
|
37
|
+
# 找到函数结束
|
|
38
|
+
brace_count = 0
|
|
39
|
+
end_pos = match.end()
|
|
40
|
+
for i, char in enumerate(content[match.end():]):
|
|
41
|
+
if char == '{':
|
|
42
|
+
brace_count += 1
|
|
43
|
+
elif char == '}':
|
|
44
|
+
if brace_count == 0:
|
|
45
|
+
end_pos = match.end() + i + 1
|
|
46
|
+
break
|
|
47
|
+
brace_count -= 1
|
|
48
|
+
|
|
49
|
+
end_line = content[:end_pos].count('\n') + 1
|
|
50
|
+
|
|
51
|
+
test_func = TestFunction(
|
|
52
|
+
name=func_name,
|
|
53
|
+
file_path=file_path,
|
|
54
|
+
start_line=start_line,
|
|
55
|
+
end_line=end_line
|
|
56
|
+
)
|
|
57
|
+
test_functions.append(test_func)
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
self.logger.error(f"解析Go文件失败 {file_path}: {e}")
|
|
61
|
+
|
|
62
|
+
return test_functions
|
|
63
|
+
|
|
64
|
+
def find_test_file(self, test_case: TestCase) -> Optional[str]:
|
|
65
|
+
"""查找Go测试文件"""
|
|
66
|
+
classname = test_case.classname
|
|
67
|
+
test_name = test_case.name
|
|
68
|
+
|
|
69
|
+
# 处理特殊的构建失败错误: [build failed]
|
|
70
|
+
if '[build failed]' in test_name or '[no test files]' in test_name:
|
|
71
|
+
# classname 包含包路径,如 "github.com/FishGoddess/logit/rotate"
|
|
72
|
+
# 尝试从包名推断目录
|
|
73
|
+
package_parts = classname.split('/')
|
|
74
|
+
if len(package_parts) > 1:
|
|
75
|
+
# 尝试不同的包目录组合
|
|
76
|
+
for i in range(len(package_parts) - 1, 0, -1):
|
|
77
|
+
subdir = '/'.join(package_parts[i:])
|
|
78
|
+
# 查找该目录下的所有测试文件
|
|
79
|
+
for test_file in Path(self.project_dir).rglob(f"{subdir}/*_test.go"):
|
|
80
|
+
if test_file.exists():
|
|
81
|
+
self.logger.info(f"[编译错误] 找到包 {classname} 的测试文件: {test_file}")
|
|
82
|
+
return str(test_file)
|
|
83
|
+
# 也尝试子目录本身
|
|
84
|
+
dir_path = Path(self.project_dir) / subdir
|
|
85
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
86
|
+
for test_file in dir_path.glob("*_test.go"):
|
|
87
|
+
if test_file.exists():
|
|
88
|
+
self.logger.info(f"[编译错误] 找到包 {classname} 的测试文件: {test_file}")
|
|
89
|
+
return str(test_file)
|
|
90
|
+
|
|
91
|
+
# 从包名推断文件路径
|
|
92
|
+
patterns = [
|
|
93
|
+
f"{classname.replace('.', '/')}_test.go",
|
|
94
|
+
f"{classname}_test.go",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
for pattern in patterns:
|
|
98
|
+
file_path = Path(self.project_dir) / pattern
|
|
99
|
+
if file_path.exists():
|
|
100
|
+
return str(file_path)
|
|
101
|
+
|
|
102
|
+
# 搜索包含测试函数的文件
|
|
103
|
+
for test_file in Path(self.project_dir).rglob("*_test.go"):
|
|
104
|
+
try:
|
|
105
|
+
with open(test_file, 'r', encoding='utf-8') as f:
|
|
106
|
+
content = f.read()
|
|
107
|
+
if f"func {test_name}(" in content:
|
|
108
|
+
return str(test_file)
|
|
109
|
+
except:
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
return None
|