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,7 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 多语言单测覆盖率统计工具包
4
+ """
5
+
6
+ __version__ = "1.0.0"
7
+ __author__ = "Coverage Tool"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 分析器包
4
+ 包含依赖分析等功能
5
+ """
6
+
7
+ from .dependency import DependencyAnalyzer, Dependency, ProjectAnalysis, DependencyType
8
+
9
+ __all__ = [
10
+ 'DependencyAnalyzer',
11
+ 'Dependency',
12
+ 'ProjectAnalysis',
13
+ 'DependencyType',
14
+ ]
@@ -0,0 +1,513 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 项目依赖分析器
4
+ 根据项目配置文件智能分析所需的编译和测试环境
5
+ """
6
+
7
+ import os
8
+ import re
9
+ import json
10
+ import xml.etree.ElementTree as ET
11
+ from dataclasses import dataclass, field
12
+ from typing import List, Dict, Set, Optional, Tuple
13
+ from pathlib import Path
14
+ from enum import Enum
15
+
16
+ class DependencyType(Enum):
17
+ """依赖类型"""
18
+ PYTHON_PACKAGE = "python_package"
19
+ JAVA_PACKAGE = "java_package"
20
+ GO_MODULE = "go_module"
21
+ NODE_PACKAGE = "node_package"
22
+ BUILD_TOOL = "build_tool"
23
+ COMPILER = "compiler"
24
+ TEST_FRAMEWORK = "test_framework"
25
+ COVERAGE_TOOL = "coverage_tool"
26
+
27
+ @dataclass
28
+ class Dependency:
29
+ """依赖项"""
30
+ name: str
31
+ type: DependencyType
32
+ version: Optional[str] = None
33
+ required: bool = True
34
+ installed: bool = False
35
+ installed_version: Optional[str] = None
36
+
37
+ @dataclass
38
+ class ProjectAnalysis:
39
+ """项目分析结果"""
40
+ project_dir: str
41
+ language: str
42
+ dependencies: List[Dependency] = field(default_factory=list)
43
+ build_system: Optional[str] = None
44
+ test_framework: Optional[str] = None
45
+ coverage_tool: Optional[str] = None
46
+ missing_dependencies: List[str] = field(default_factory=list)
47
+ warnings: List[str] = field(default_factory=list)
48
+
49
+ class DependencyAnalyzer:
50
+ """依赖分析器"""
51
+
52
+ # 各语言的配置文件映射
53
+ CONFIG_PATTERNS = {
54
+ 'python': [
55
+ ('requirements.txt', 'pip'),
56
+ ('setup.py', 'pip'),
57
+ ('pyproject.toml', 'pip'),
58
+ ('Pipfile', 'pip'),
59
+ ('setup.cfg', 'pip'),
60
+ ],
61
+ 'java': [
62
+ ('pom.xml', 'maven'),
63
+ ('build.gradle', 'gradle'),
64
+ ('build.gradle.kts', 'gradle'),
65
+ ('pom.xml', 'java'),
66
+ ],
67
+ 'go': [
68
+ ('go.mod', 'go'),
69
+ ('go.sum', 'go'),
70
+ ],
71
+ 'c': [
72
+ ('Makefile', 'make'),
73
+ ('CMakeLists.txt', 'cmake'),
74
+ ('*.mk', 'make'),
75
+ ],
76
+ 'cpp': [
77
+ ('Makefile', 'make'),
78
+ ('CMakeLists.txt', 'cmake'),
79
+ ('*.cmake', 'cmake'),
80
+ ],
81
+ }
82
+
83
+ # 关键依赖检测 (只检查系统级工具,不检查库依赖)
84
+ KEY_DEPENDENCIES = {
85
+ 'python': [
86
+ ('python', DependencyType.COMPILER),
87
+ ],
88
+ 'java': [
89
+ ('java', DependencyType.COMPILER),
90
+ ('maven', DependencyType.BUILD_TOOL),
91
+ ],
92
+ 'go': [
93
+ ('go', DependencyType.COMPILER),
94
+ ],
95
+ 'c': [
96
+ ('gcc', DependencyType.COMPILER),
97
+ ('make', DependencyType.BUILD_TOOL),
98
+ ],
99
+ 'cpp': [
100
+ ('g++', DependencyType.COMPILER),
101
+ ('make', DependencyType.BUILD_TOOL),
102
+ ],
103
+ }
104
+
105
+ def __init__(self, project_dir: str):
106
+ self.project_dir = project_dir
107
+ self.analysis: Optional[ProjectAnalysis] = None
108
+
109
+ def analyze(self, language: str) -> ProjectAnalysis:
110
+ """分析项目依赖"""
111
+ self.analysis = ProjectAnalysis(
112
+ project_dir=self.project_dir,
113
+ language=language
114
+ )
115
+
116
+ # 查找配置文件
117
+ self._find_config_files(language)
118
+
119
+ # 确保analysis不为None
120
+ if self.analysis is None:
121
+ self.analysis = ProjectAnalysis(
122
+ project_dir=self.project_dir,
123
+ language=language
124
+ )
125
+
126
+ # 分析具体配置文件
127
+ self._parse_config_files(language)
128
+
129
+ # 检查关键依赖
130
+ self._check_key_dependencies(language)
131
+
132
+ return self.analysis
133
+
134
+ def _find_config_files(self, language: str):
135
+ """查找配置文件"""
136
+ if self.analysis is None:
137
+ return
138
+ patterns = self.CONFIG_PATTERNS.get(language, [])
139
+
140
+ for pattern, build_system in patterns:
141
+ if '*' in pattern:
142
+ # 匹配通配符
143
+ files = Path(self.project_dir).glob(pattern)
144
+ for f in files:
145
+ self.analysis.build_system = build_system
146
+ else:
147
+ # 精确匹配
148
+ config_path = os.path.join(self.project_dir, pattern)
149
+ if os.path.exists(config_path):
150
+ self.analysis.build_system = build_system
151
+ break
152
+
153
+ def _parse_config_files(self, language: str):
154
+ """解析配置文件"""
155
+ project_path = Path(self.project_dir)
156
+
157
+ if language == 'python':
158
+ self._parse_python_configs(project_path)
159
+ elif language == 'java':
160
+ self._parse_java_configs(project_path)
161
+ elif language == 'go':
162
+ self._parse_go_configs(project_path)
163
+ elif language in ['c', 'cpp']:
164
+ self._parse_c_cpp_configs(project_path)
165
+
166
+ def _parse_python_configs(self, project_path: Path):
167
+ """解析Python配置文件"""
168
+ # requirements.txt
169
+ req_file = project_path / 'requirements.txt'
170
+ if req_file.exists():
171
+ self._parse_requirements(req_file)
172
+
173
+ # setup.py
174
+ setup_file = project_path / 'setup.py'
175
+ if setup_file.exists():
176
+ self._parse_setup_py(setup_file)
177
+
178
+ # pyproject.toml
179
+ pyproject_file = project_path / 'pyproject.toml'
180
+ if pyproject_file.exists():
181
+ self._parse_pyproject_toml(pyproject_file)
182
+
183
+ def _parse_requirements(self, file_path: Path):
184
+ """解析requirements.txt"""
185
+ try:
186
+ with open(file_path, 'r', encoding='utf-8') as f:
187
+ for line in f:
188
+ line = line.strip()
189
+ if line and not line.startswith('#'):
190
+ # 解析版本要求
191
+ name, version = self._parse_package_line(line)
192
+ dep = Dependency(
193
+ name=name,
194
+ type=DependencyType.PYTHON_PACKAGE,
195
+ version=version,
196
+ required=False
197
+ )
198
+ self.analysis.dependencies.append(dep)
199
+ except Exception as e:
200
+ self.analysis.warnings.append(f"无法解析 requirements.txt: {e}")
201
+
202
+ def _parse_setup_py(self, file_path: Path):
203
+ """解析setup.py"""
204
+ try:
205
+ with open(file_path, 'r', encoding='utf-8') as f:
206
+ content = f.read()
207
+
208
+ # 查找install_requires
209
+ match = re.search(r'install_requires\s*=\s*\[(.*?)\]', content, re.DOTALL)
210
+ if match:
211
+ deps_str = match.group(1)
212
+ for line in deps_str.split('\n'):
213
+ line = line.strip().strip("'\"")
214
+ if line and line.startswith('['):
215
+ line = line.strip('[]')
216
+ if line:
217
+ name, version = self._parse_package_line(line)
218
+ dep = Dependency(
219
+ name=name,
220
+ type=DependencyType.PYTHON_PACKAGE,
221
+ version=version,
222
+ required=False
223
+ )
224
+ self.analysis.dependencies.append(dep)
225
+
226
+ # 查找test_requires
227
+ match = re.search(r'test_requires\s*=\s*\[(.*?)\]', content, re.DOTALL)
228
+ if match:
229
+ deps_str = match.group(1)
230
+ for line in deps_str.split('\n'):
231
+ line = line.strip().strip("'\"")
232
+ if line and line.startswith('['):
233
+ line = line.strip('[]')
234
+ if line:
235
+ name, version = self._parse_package_line(line)
236
+ dep = Dependency(
237
+ name=name,
238
+ type=DependencyType.TEST_FRAMEWORK,
239
+ version=version,
240
+ required=False
241
+ )
242
+ self.analysis.test_framework = name
243
+ self.analysis.dependencies.append(dep)
244
+ except Exception as e:
245
+ self.analysis.warnings.append(f"无法解析 setup.py: {e}")
246
+
247
+ def _parse_pyproject_toml(self, file_path: Path):
248
+ """解析pyproject.toml"""
249
+ try:
250
+ with open(file_path, 'r', encoding='utf-8') as f:
251
+ content = f.read()
252
+
253
+ # 查找测试依赖
254
+ match = re.search(r'\[tool\.pytest\]', content)
255
+ if match:
256
+ self.analysis.test_framework = 'pytest'
257
+
258
+ # 查找依赖
259
+ match = re.search(r'dependencies\s*=\s*\[(.*?)\]', content, re.DOTALL)
260
+ if match:
261
+ deps_str = match.group(1)
262
+ for line in deps_str.split('\n'):
263
+ line = line.strip().strip("'\"")
264
+ if line:
265
+ name, version = self._parse_package_line(line)
266
+ dep = Dependency(
267
+ name=name,
268
+ type=DependencyType.PYTHON_PACKAGE,
269
+ version=version,
270
+ required=False
271
+ )
272
+ self.analysis.dependencies.append(dep)
273
+ except Exception as e:
274
+ self.analysis.warnings.append(f"无法解析 pyproject.toml: {e}")
275
+
276
+ def _parse_package_line(self, line: str) -> Tuple[str, Optional[str]]:
277
+ """解析包名和版本"""
278
+ line = line.strip()
279
+ if '>=' in line:
280
+ name, version = line.split('>=')
281
+ return name.strip(), f">={version.strip()}"
282
+ elif '<=' in line:
283
+ name, version = line.split('<=')
284
+ return name.strip(), f"<={version.strip()}"
285
+ elif '==' in line:
286
+ name, version = line.split('==')
287
+ return name.strip(), version.strip()
288
+ elif '>' in line:
289
+ name, version = line.split('>')
290
+ return name.strip(), f">{version.strip()}"
291
+ elif '<' in line:
292
+ name, version = line.split('<')
293
+ return name.strip(), f"<{version.strip()}"
294
+ else:
295
+ return line, None
296
+
297
+ def _parse_java_configs(self, project_path: Path):
298
+ """解析Java配置文件"""
299
+ pom_file = project_path / 'pom.xml'
300
+ if pom_file.exists():
301
+ try:
302
+ tree = ET.parse(pom_file)
303
+ root = tree.getroot()
304
+
305
+ # 解析命名空间
306
+ ns = {'m': 'http://maven.apache.org/POM/4.0.0'}
307
+
308
+ # 获取坐标
309
+ group_id = root.find('m:groupId', ns) or root.find('groupId')
310
+ artifact_id = root.find('m:artifactId', ns) or root.find('artifactId')
311
+ version = root.find('m:version', ns) or root.find('version')
312
+
313
+ if artifact_id is not None:
314
+ self.analysis.build_system = 'maven'
315
+
316
+ # 查找测试依赖
317
+ for dep in root.findall('.//m:dependency', ns):
318
+ dep_group = dep.find('m:groupId', ns) or dep.find('groupId')
319
+ dep_artifact = dep.find('m:artifactId', ns) or dep.find('artifactId')
320
+ dep_version = dep.find('m:version', ns) or dep.find('version')
321
+
322
+ if dep_artifact is not None:
323
+ dep_name = dep_artifact.text or ""
324
+ is_test = False
325
+
326
+ # 检查是否是测试依赖
327
+ for scope in dep.findall('m:scope', ns) or dep.findall('scope'):
328
+ if scope.text == 'test':
329
+ is_test = True
330
+ break
331
+
332
+ # 检查测试框架
333
+ test_frameworks = ['junit', 'testng', 'mockito', 'assertj']
334
+ if any(tf in dep_name.lower() for tf in test_frameworks):
335
+ self.analysis.test_framework = dep_name
336
+
337
+ dep_type = DependencyType.TEST_FRAMEWORK if is_test else DependencyType.JAVA_PACKAGE
338
+ dep = Dependency(
339
+ name=dep_name,
340
+ type=dep_type,
341
+ version=dep_version.text if dep_version is not None else None,
342
+ required=False
343
+ )
344
+ self.analysis.dependencies.append(dep)
345
+
346
+ except Exception as e:
347
+ self.analysis.warnings.append(f"无法解析 pom.xml: {e}")
348
+
349
+ def _parse_go_configs(self, project_path: Path):
350
+ """解析Go配置文件"""
351
+ go_mod_file = project_path / 'go.mod'
352
+ if go_mod_file.exists():
353
+ try:
354
+ with open(go_mod_file, 'r', encoding='utf-8') as f:
355
+ content = f.read()
356
+
357
+ # 查找go.mod版本
358
+ match = re.search(r'go\s+(\d+\.\d+)', content)
359
+ if match:
360
+ self.analysis.build_system = f"go {match.group(1)}"
361
+
362
+ # 查找测试框架
363
+ test_imports = ['testing', 'testify', 'goconvey', 'gocheck']
364
+ for line in content.split('\n'):
365
+ if 'require' in line.lower():
366
+ for test_pkg in test_imports:
367
+ if test_pkg in line.lower():
368
+ self.analysis.test_framework = test_pkg
369
+ break
370
+
371
+ except Exception as e:
372
+ self.analysis.warnings.append(f"无法解析 go.mod: {e}")
373
+
374
+ def _parse_c_cpp_configs(self, project_path: Path):
375
+ """解析C/C++配置文件"""
376
+ # 检查Makefile
377
+ makefile = project_path / 'Makefile'
378
+ if makefile.exists():
379
+ self.analysis.build_system = 'make'
380
+ with open(makefile, 'r', encoding='utf-8') as f:
381
+ content = f.read()
382
+
383
+ # 检查测试框架
384
+ if 'gtest' in content.lower() or 'googletest' in content.lower():
385
+ self.analysis.test_framework = 'googletest'
386
+ elif 'catch' in content.lower():
387
+ self.analysis.test_framework = 'catch2'
388
+ elif 'unity' in content.lower():
389
+ self.analysis.test_framework = 'unity'
390
+
391
+ # 检查CMakeLists.txt
392
+ cmake_file = project_path / 'CMakeLists.txt'
393
+ if cmake_file.exists() and not self.analysis.build_system:
394
+ self.analysis.build_system = 'cmake'
395
+
396
+ with open(cmake_file, 'r', encoding='utf-8') as f:
397
+ content = f.read()
398
+
399
+ # 检查测试框架
400
+ if 'GoogleTest' in content or 'gtest' in content.lower():
401
+ self.analysis.test_framework = 'googletest'
402
+ elif 'Catch' in content:
403
+ self.analysis.test_framework = 'catch2'
404
+
405
+ def _check_key_dependencies(self, language: str):
406
+ """检查关键依赖是否安装"""
407
+ key_deps = self.KEY_DEPENDENCIES.get(language, [])
408
+
409
+ for dep_name, dep_type in key_deps:
410
+ dep = Dependency(
411
+ name=dep_name,
412
+ type=dep_type,
413
+ required=True
414
+ )
415
+
416
+ # 检查是否已安装
417
+ installed, version = self._check_dependency_installed(dep_name, language)
418
+ dep.installed = installed
419
+ dep.installed_version = version
420
+
421
+ self.analysis.dependencies.append(dep)
422
+
423
+ if not installed:
424
+ self.analysis.missing_dependencies.append(dep_name)
425
+
426
+ def _check_dependency_installed(self, dep_name: str, language: str) -> Tuple[bool, Optional[str]]:
427
+ """检查依赖是否已安装"""
428
+ import subprocess
429
+
430
+ commands = {
431
+ 'python': f'python3 --version 2>&1',
432
+ 'java': f'java -version 2>&1',
433
+ 'go': f'go version',
434
+ 'gcc': f'gcc --version',
435
+ 'g++': f'g++ --version',
436
+ 'make': f'make --version',
437
+ 'maven': f'mvn -version',
438
+ }
439
+
440
+ cmd = commands.get(dep_name)
441
+ if cmd:
442
+ try:
443
+ result = subprocess.run(
444
+ cmd,
445
+ shell=True,
446
+ capture_output=True,
447
+ text=True,
448
+ timeout=5
449
+ )
450
+
451
+ if result.returncode == 0:
452
+ # 尝试提取版本
453
+ output = result.stdout or result.stderr
454
+ version_match = re.search(r'(\d+\.\d+\.?\d*)', output)
455
+ version = version_match.group(1) if version_match else None
456
+ return True, version
457
+ except:
458
+ pass
459
+
460
+ return False, None
461
+
462
+ def get_installation_suggestions(self) -> List[str]:
463
+ """获取安装建议"""
464
+ suggestions = []
465
+
466
+ if not self.analysis:
467
+ return suggestions
468
+
469
+ dep_mapping = {
470
+ 'pytest': {
471
+ 'command': 'pip install pytest',
472
+ 'description': 'Python测试框架'
473
+ },
474
+ 'coverage': {
475
+ 'command': 'pip install coverage',
476
+ 'description': 'Python覆盖率工具'
477
+ },
478
+ 'tox': {
479
+ 'command': 'pip install tox',
480
+ 'description': 'Python自动化测试工具'
481
+ },
482
+ 'maven': {
483
+ 'command': 'apt install maven # Ubuntu/Debian\nbrew install maven # macOS',
484
+ 'description': 'Java构建工具'
485
+ },
486
+ 'gcc': {
487
+ 'command': 'apt install gcc # Ubuntu/Debian\nbrew install gcc # macOS',
488
+ 'description': 'C/C++编译器'
489
+ },
490
+ 'g++': {
491
+ 'command': 'apt install g++ # Ubuntu/Debian\nbrew install gcc # macOS',
492
+ 'description': 'C++编译器'
493
+ },
494
+ 'make': {
495
+ 'command': 'apt install make # Ubuntu/Debian\nbrew install make # macOS',
496
+ 'description': '构建工具'
497
+ },
498
+ 'lcov': {
499
+ 'command': 'apt install lcov # Ubuntu/Debian',
500
+ 'description': '代码覆盖率工具'
501
+ },
502
+ 'googletest': {
503
+ 'command': 'apt install libgtest-dev cmake\ncd /usr/src/gtest && cmake . && make && cp *.a /usr/lib/',
504
+ 'description': 'C++测试框架'
505
+ },
506
+ }
507
+
508
+ for dep_name in self.analysis.missing_dependencies:
509
+ if dep_name in dep_mapping:
510
+ info = dep_mapping[dep_name]
511
+ suggestions.append(f"{dep_name}: {info['description']}\n 安装: {info['command']}")
512
+
513
+ return suggestions
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ JUnit XML 报告转换器包
4
+ 将各种格式的测试输出转换为统一的 JUnit XML 格式
5
+ """
6
+
7
+ from typing import List, Optional
8
+
9
+ from .base import TestCaseResult, TestSuiteResult, BaseConverter
10
+ from .go_converter import GoTestConverter, GoJUnitReportTool
11
+ from .gtest_converter import GTestConverter
12
+ from .cunit_converter import CUnitConverter
13
+ from .generic_converter import GenericConverter
14
+ from .factory import ConverterFactory
15
+
16
+
17
+ # 保持向后兼容的 JUnitReportConverter 类
18
+ class JUnitReportConverter(BaseConverter):
19
+ """JUnit XML 报告转换器 (向后兼容)"""
20
+
21
+ @staticmethod
22
+ def convert_go_test(go_test_output: str, coverage_out: Optional[str] = None) -> str:
23
+ """将 Go test JSON 输出转换为 JUnit XML"""
24
+ return GoTestConverter.convert(go_test_output, coverage_out)
25
+
26
+ @staticmethod
27
+ def convert_cpp_gtest(gtest_output: str, xml_path: str) -> str:
28
+ """转换 Google Test 输出为 JUnit XML"""
29
+ return GTestConverter.convert(gtest_output, xml_path)
30
+
31
+ @staticmethod
32
+ def convert_cunit(cunit_output: str) -> str:
33
+ """转换 CUnit 输出为 JUnit XML"""
34
+ return CUnitConverter.convert(cunit_output)
35
+
36
+ @staticmethod
37
+ def convert_generic_output(output: str, test_framework: str) -> str:
38
+ """通用测试输出转换"""
39
+ return GenericConverter.convert(output, test_framework)
40
+
41
+
42
+ # 保持向后兼容的 GoJUnitReport 类
43
+ class GoJUnitReport(GoJUnitReportTool):
44
+ """Go 测试 JUnit 报告生成器 (向后兼容)"""
45
+ pass
46
+
47
+
48
+ __all__ = [
49
+ 'TestCaseResult',
50
+ 'TestSuiteResult',
51
+ 'BaseConverter',
52
+ 'GoTestConverter',
53
+ 'GoJUnitReportTool',
54
+ 'GTestConverter',
55
+ 'CUnitConverter',
56
+ 'GenericConverter',
57
+ 'ConverterFactory',
58
+ 'JUnitReportConverter',
59
+ 'GoJUnitReport',
60
+ ]
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ JUnit XML 报告转换器 - 基础模块
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Optional
8
+
9
+
10
+ @dataclass
11
+ class TestCaseResult:
12
+ """测试用例结果"""
13
+ name: str
14
+ classname: str
15
+ time: float
16
+ passed: bool = True
17
+ failed_message: Optional[str] = None
18
+ failed_type: Optional[str] = None
19
+ error_message: Optional[str] = None
20
+ error_type: Optional[str] = None
21
+ skipped: bool = False
22
+ skipped_message: Optional[str] = None
23
+
24
+
25
+ @dataclass
26
+ class TestSuiteResult:
27
+ """测试套件结果"""
28
+ name: str
29
+ tests: int = 0
30
+ failures: int = 0
31
+ errors: int = 0
32
+ skipped: int = 0
33
+ time: float = 0.0
34
+ test_cases: List[TestCaseResult] = field(default_factory=list)
35
+
36
+
37
+ class BaseConverter:
38
+ """报告转换器基类"""
39
+
40
+ @staticmethod
41
+ def _create_junit_xml(suite_name: str, test_cases: List[TestCaseResult],
42
+ total_time: float) -> str:
43
+ """创建 JUnit XML"""
44
+ tests = len(test_cases)
45
+ failures = sum(1 for tc in test_cases if not tc.passed and not tc.skipped)
46
+ errors = sum(1 for tc in test_cases if not tc.passed and tc.skipped)
47
+ skipped = sum(1 for tc in test_cases if tc.skipped)
48
+
49
+ lines = [
50
+ '<?xml version="1.0" encoding="UTF-8"?>',
51
+ f'<testsuites name="Test Results" tests="{tests}" failures="{failures}" errors="{errors}" skipped="{skipped}">',
52
+ f' <testsuite name="{suite_name}" tests="{tests}" failures="{failures}" errors="{errors}" skipped="{skipped}" time="{total_time:.3f}">',
53
+ ]
54
+
55
+ for case in test_cases:
56
+ status_attr = ''
57
+ content = ''
58
+
59
+ if case.skipped:
60
+ status_attr = f' status="skipped"'
61
+ content = f' <skipped message="{case.skipped_message or ""}"/>'
62
+ elif not case.passed:
63
+ status_attr = f' status="failure"'
64
+ if case.failed_message:
65
+ content = f' <failure message="{case.failed_message}" type="{case.failed_type or "failure"}"/>'
66
+ elif case.error_message:
67
+ content = f' <error message="{case.error_message}" type="{case.error_type or "error"}"/>'
68
+
69
+ lines.append(f' <testcase name="{case.name}" classname="{case.classname}" time="{case.time:.3f}"{status_attr}>')
70
+ lines.append(content)
71
+ lines.append(f' </testcase>')
72
+
73
+ lines.extend([
74
+ ' </testsuite>',
75
+ '</testsuites>'
76
+ ])
77
+
78
+ return '\n'.join(lines)
79
+
80
+ @staticmethod
81
+ def _create_basic_junit_xml(suite_name: str, test_cases: List[TestCaseResult]) -> str:
82
+ """创建基本的 JUnit XML (用于错误情况)"""
83
+ return BaseConverter._create_junit_xml(suite_name, test_cases, 0.0)