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.
Files changed (47) hide show
  1. coverage_tool/__init__.py +7 -0
  2. coverage_tool/analyzers/__init__.py +14 -0
  3. coverage_tool/analyzers/dependency.py +513 -0
  4. coverage_tool/converters/__init__.py +60 -0
  5. coverage_tool/converters/base.py +83 -0
  6. coverage_tool/converters/cunit_converter.py +47 -0
  7. coverage_tool/converters/factory.py +36 -0
  8. coverage_tool/converters/generic_converter.py +62 -0
  9. coverage_tool/converters/go_converter.py +178 -0
  10. coverage_tool/converters/gtest_converter.py +63 -0
  11. coverage_tool/core/__init__.py +18 -0
  12. coverage_tool/core/config.py +66 -0
  13. coverage_tool/core/reporter.py +270 -0
  14. coverage_tool/example.py +102 -0
  15. coverage_tool/handlers/__init__.py +13 -0
  16. coverage_tool/handlers/compilation.py +322 -0
  17. coverage_tool/handlers/env_checker.py +312 -0
  18. coverage_tool/main.py +559 -0
  19. coverage_tool/parsers/__init__.py +15 -0
  20. coverage_tool/parsers/junit.py +172 -0
  21. coverage_tool/runners/__init__.py +26 -0
  22. coverage_tool/runners/base.py +249 -0
  23. coverage_tool/runners/c_runner.py +66 -0
  24. coverage_tool/runners/cpp_runner.py +65 -0
  25. coverage_tool/runners/factory.py +41 -0
  26. coverage_tool/runners/go_runner.py +158 -0
  27. coverage_tool/runners/java_runner.py +54 -0
  28. coverage_tool/runners/python_runner.py +99 -0
  29. coverage_tool/test_removers/__init__.py +38 -0
  30. coverage_tool/test_removers/base.py +233 -0
  31. coverage_tool/test_removers/c_remover.py +210 -0
  32. coverage_tool/test_removers/cpp_remover.py +272 -0
  33. coverage_tool/test_removers/factory.py +45 -0
  34. coverage_tool/test_removers/go_remover.py +112 -0
  35. coverage_tool/test_removers/java_remover.py +216 -0
  36. coverage_tool/test_removers/python_remover.py +172 -0
  37. coverage_tool/utils/__init__.py +23 -0
  38. coverage_tool/utils/dependency_graph.py +52 -0
  39. coverage_tool/utils/file_backup.py +112 -0
  40. coverage_tool/utils/helpers.py +89 -0
  41. coverage_tool/utils/logger.py +54 -0
  42. coverage_tool/utils/progress.py +41 -0
  43. coverage_tool-1.0.0.dist-info/METADATA +484 -0
  44. coverage_tool-1.0.0.dist-info/RECORD +47 -0
  45. coverage_tool-1.0.0.dist-info/WHEEL +5 -0
  46. coverage_tool-1.0.0.dist-info/entry_points.txt +2 -0
  47. 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