test-coverage-analyzer 0.1.3__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.
@@ -0,0 +1,30 @@
1
+ """
2
+ Test Coverage Analyzer - 多语言测试文件查找和覆盖率分析工具
3
+ """
4
+
5
+ __version__ = "0.1.3"
6
+ __author__ = "xisang"
7
+ __email__ = "xisang@gmail.com"
8
+
9
+ from .core import (
10
+ TestFileMatch,
11
+ CoverageResult,
12
+ TestFileFinder,
13
+ CodeAnalyzer,
14
+ CoverageAnalyzer,
15
+ save_results_to_txt,
16
+ save_results_to_json
17
+ )
18
+
19
+ from .cli import main
20
+
21
+ __all__ = [
22
+ "TestFileMatch",
23
+ "CoverageResult",
24
+ "TestFileFinder",
25
+ "CodeAnalyzer",
26
+ "CoverageAnalyzer",
27
+ "save_results_to_txt",
28
+ "save_results_to_json",
29
+ "main"
30
+ ]
@@ -0,0 +1,213 @@
1
+ """命令行接口"""
2
+
3
+ import sys
4
+ import os
5
+ import json
6
+ from pathlib import Path
7
+ from .core import (
8
+ TestFileFinder,
9
+ CoverageAnalyzer,
10
+ process_coverage_data,
11
+ save_results_to_json,
12
+ to_json
13
+ )
14
+ import uuid
15
+
16
+ def json2prompt(json_data):
17
+ """
18
+ 将JSON数据转换为prompt格式的处理函数
19
+ 返回结构化的prompt数据(字典格式)
20
+ """
21
+ prompt_data = []
22
+
23
+ if 'coverage_results' in json_data and len(json_data['coverage_results'])>0:
24
+ for item in json_data['coverage_results']:
25
+ prompt_data.append({
26
+ 'id': str(uuid.uuid4().hex),
27
+ 'prompt': f'''请给{item["language"]}代码文件{item["source_file"]}生成增量单元测试用例,它对应的测试文件为{item["test_file"]},当前测试代码未覆盖的测试元素为{item["uncovered_elements"]},待测文件的圈复杂度为{item["cyclomatic_complexity"]},依赖复杂度为{item["dependency_complexity"]}。接下来你要根据给定的文件的复杂度增量生成单元测试,使得待测文件的覆盖率尽可能高。'''
28
+ })
29
+
30
+ if 'unmatched' in json_data and len(json_data['unmatched'])>0:
31
+ for item in json_data['unmatched']:
32
+ prompt_data.append({
33
+ 'id': str(uuid.uuid4().hex),
34
+ 'prompt': f'''请给{item["language"]}代码文件{item["source_file"]}生成全量单元测试用例,待测文件的圈复杂度为{item["cyclomatic_complexity"]},依赖复杂度为{item["dependency_complexity"]}。接下来你要根据给定的文件的复杂度全量生成单元测试,使得待测文件的覆盖率尽可能高。'''
35
+ })
36
+
37
+ return prompt_data
38
+
39
+ def load_json_file(json_path):
40
+ """加载JSON文件"""
41
+ with open(json_path, 'r', encoding='utf-8') as f:
42
+ return json.load(f)
43
+
44
+ def save_prompt_json(prompt_data, output_dir, file_prefix):
45
+ """保存prompt数据为JSON文件"""
46
+ prompt_json_path = os.path.join(output_dir, f"{file_prefix}_prompt.json")
47
+ with open(prompt_json_path, 'w', encoding='utf-8') as f:
48
+ json.dump(prompt_data, f, ensure_ascii=False, indent=2)
49
+
50
+ return prompt_json_path
51
+
52
+ def main():
53
+ """主函数,提供命令行接口"""
54
+ # 解析命令行参数
55
+ if len(sys.argv) < 2 or len(sys.argv) > 7:
56
+ print("用法: test-coverage-analyzer <源代码路径> [输出目录] [文件名前缀] [--threshold <阈值>] [--prompt/--no-prompt]")
57
+ print("参数说明:")
58
+ print(" --threshold <阈值>: 设置覆盖率阈值(0-100的数字),过滤覆盖率高于阈值的文件")
59
+ print(" --prompt: 对JSON文件进行二次处理生成prompt格式(默认)")
60
+ print(" --no-prompt: 不进行JSON二次处理")
61
+ print("示例:")
62
+ print(" test-coverage-analyzer ./my_project")
63
+ print(" test-coverage-analyzer ./my_project ./reports my_result")
64
+ print(" test-coverage-analyzer ./my_project ./reports my_result --threshold 80 --no-prompt")
65
+ print(" test-coverage-analyzer ./my_project ./reports my_result --threshold 50")
66
+ sys.exit(1)
67
+
68
+ # 获取参数
69
+ source_path = sys.argv[1]
70
+
71
+ # 设置默认值
72
+ output_dir = "./"
73
+ file_prefix = "scanner_result"
74
+ prompt = True # 默认开启prompt处理
75
+ threshold = None # 默认无阈值过滤
76
+
77
+ # 解析可选参数
78
+ arg_index = 2
79
+ while arg_index < len(sys.argv):
80
+ arg = sys.argv[arg_index]
81
+
82
+ if arg == '--threshold':
83
+ arg_index += 1
84
+ if arg_index >= len(sys.argv):
85
+ print("错误: --threshold 参数需要指定一个数值")
86
+ sys.exit(1)
87
+ try:
88
+ threshold = float(sys.argv[arg_index])
89
+ if threshold < 0 or threshold > 100:
90
+ print("错误: 阈值必须在0-100之间")
91
+ sys.exit(1)
92
+ except ValueError:
93
+ print(f"错误: --threshold 参数必须是数字,得到: {sys.argv[arg_index]}")
94
+ sys.exit(1)
95
+ elif arg == '--no-prompt':
96
+ prompt = False
97
+ elif arg == '--prompt':
98
+ prompt = True
99
+ elif not arg.startswith('--'):
100
+ # 位置参数:输出目录和文件名前缀
101
+ if output_dir == "./":
102
+ output_dir = arg
103
+ elif file_prefix == "scanner_result":
104
+ file_prefix = arg
105
+ else:
106
+ print(f"错误: 无效参数 '{arg}'")
107
+ sys.exit(1)
108
+ else:
109
+ print(f"错误: 未知参数 '{arg}'")
110
+ sys.exit(1)
111
+
112
+ arg_index += 1
113
+
114
+ # 验证源路径
115
+ if not os.path.exists(source_path):
116
+ print(f"错误: 源路径 '{source_path}' 不存在")
117
+ sys.exit(1)
118
+
119
+ # 创建输出目录(如果不存在)
120
+ try:
121
+ Path(output_dir).mkdir(parents=True, exist_ok=True)
122
+ except Exception as e:
123
+ print(f"错误: 无法创建输出目录 '{output_dir}': {e}")
124
+ sys.exit(1)
125
+
126
+ # 构建完整的文件路径
127
+ json_path = os.path.join(output_dir, f"{file_prefix}.json")
128
+
129
+ # 执行分析
130
+ finder = TestFileFinder()
131
+ analyzer = CoverageAnalyzer()
132
+
133
+ try:
134
+ print(f"正在分析路径: {source_path}")
135
+ print("查找源文件和测试文件配对...")
136
+
137
+ matches, unmatched = finder.find_test_files(source_path)
138
+
139
+ print(f"找到 {len(matches)} 个配对,{len(unmatched)} 个未匹配文件")
140
+ print("分析覆盖率和代码复杂度...")
141
+
142
+ # 分析覆盖率
143
+ coverage_results = []
144
+ for match in matches:
145
+ coverage_result = analyzer.analyze_coverage(
146
+ match.source_file,
147
+ match.test_file,
148
+ match.language
149
+ )
150
+ coverage_results.append(coverage_result)
151
+
152
+ # 应用阈值过滤
153
+ if threshold is not None:
154
+ print(f"应用阈值过滤 ({threshold}%)...")
155
+ filtered_matches = []
156
+ filtered_coverage_results = []
157
+
158
+ for match, coverage_result in zip(matches, coverage_results):
159
+ if coverage_result.coverage_percentage <= threshold:
160
+ filtered_matches.append(match)
161
+ filtered_coverage_results.append(coverage_result)
162
+
163
+ matches = filtered_matches
164
+ coverage_results = filtered_coverage_results
165
+ print(f"过滤后剩余 {len(matches)} 个配对文件")
166
+
167
+ # 分析未匹配文件的复杂度
168
+ unmatched_results = []
169
+ for source_file, lang in unmatched:
170
+ unmatched_result = analyzer.analyze_unmatched_file(source_file, lang)
171
+ unmatched_results.append(unmatched_result)
172
+
173
+ # 保存结果
174
+ #save_results_to_txt(matches, unmatched, coverage_results, unmatched_results, txt_path)
175
+ json_data = save_results_to_json(matches, unmatched, coverage_results, unmatched_results)
176
+
177
+ # 如果开启prompt处理
178
+ if prompt:
179
+ print("正在处理JSON数据生成prompt格式...")
180
+ # 转换为prompt格式
181
+ prompt_data = json2prompt(json_data)
182
+ # 保存prompt文件
183
+ prompt_json_path = save_prompt_json(prompt_data, output_dir, file_prefix)
184
+ print(f" Prompt JSON文件: {prompt_json_path}")
185
+
186
+ json_results = process_coverage_data(json_data)
187
+ to_json(json_path, json_results)
188
+ print(f"结果已保存到:")
189
+ print(f" JSON文件: {json_path}")
190
+ print(f"\n=== 分析结果摘要 ===")
191
+ print(f"源文件总数: {len(matches) + len(unmatched)}")
192
+ print(f"已匹配测试文件: {len(matches)}")
193
+ print(f"未匹配测试文件: {len(unmatched)}")
194
+
195
+ if coverage_results:
196
+ avg_coverage = sum(cr.coverage_percentage for cr in coverage_results) / len(coverage_results) if coverage_results else 0
197
+ print(f"平均覆盖率: {avg_coverage:.2f}%")
198
+
199
+ if unmatched_results:
200
+ avg_cyclomatic = sum(ur.cyclomatic_complexity for ur in unmatched_results) / len(unmatched_results) if unmatched_results else 0
201
+ print(f"未匹配文件平均圈复杂度: {avg_cyclomatic:.2f}")
202
+
203
+ if threshold is not None:
204
+ print(f"应用的阈值: {threshold}%")
205
+
206
+ except Exception as e:
207
+ print(f"错误: {e}")
208
+ import traceback
209
+ traceback.print_exc()
210
+ sys.exit(1)
211
+
212
+ if __name__ == "__main__":
213
+ main()