""" 测试用例生成模块 """ import re from pathlib import Path from typing import List, Optional from .models import CppcheckIssue, CodeContext # 复用 test.py 中已配置好的 OpenAI client(DeepSeek) try: from test import client # type: ignore except Exception as import_error: # noqa: PIE786 client = None # 延迟到生成阶段再报错 def generate_issue_specific_test_code(issue: CppcheckIssue) -> str: """根据问题类型生成具体的测试代码""" issue_id = issue.id.lower() test_codes = { 'memleak': '''void test_memleak() { // 模拟内存泄漏场景 int *p = new int[100]; for (int i = 0; i < 100; i++) { p[i] = i; } // 故意不释放内存,制造内存泄漏 // delete [] p; // 这行被注释掉 printf("内存已分配但未释放 - 预期内存泄漏\\n"); }''', 'arrayindexoutofbounds': '''void test_arrayIndexOutOfBounds() { // 模拟数组越界场景 int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 故意访问越界索引 int value = arr[10]; // 越界访问 printf("访问越界索引 10,值: %d\\n", value); }''', 'nullpointer': '''void test_nullPointer() { // 模拟空指针解引用场景 int *ptr = nullptr; // 故意解引用空指针 int value = *ptr; // 空指针解引用 printf("解引用空指针,值: %d\\n", value); }''', 'uninitvar': '''void test_uninitvar() { // 模拟未初始化变量场景 int x; // 未初始化 // 故意使用未初始化的变量 printf("未初始化变量的值: %d\\n", x); }''', 'doublefree': '''void test_doubleFree() { // 模拟重复释放场景 char *buf = new char[100]; delete [] buf; // 故意重复释放 delete [] buf; // 重复释放 printf("重复释放完成\\n"); }''', 'mismatchallocdealloc': '''void test_mismatchAllocDealloc() { // 模拟分配/释放不匹配场景 int *ptr = new int; // 故意使用不匹配的释放函数 free(ptr); // 应该用 delete printf("分配/释放不匹配完成\\n"); }''' } # 查找匹配的测试代码 for key, code in test_codes.items(): if key in issue_id: return code # 默认测试代码 return f'''void test_{issue.id}() {{ // 通用测试代码 printf("Testing {issue.id}...\\n"); // 在这里添加能触发{issue.id}检测的代码 // 原始问题: {issue.message} }}''' def get_issue_specific_template(issue: CppcheckIssue, project_root: Optional[Path] = None, include_dirs: List[str] = None) -> str: """根据cppcheck问题类型生成基于原项目的集成测试用例模板""" issue_id = issue.id.lower() # 从原项目源码中提取真实的问题上下文 from .analysis import extract_issue_context_from_source issue_context = extract_issue_context_from_source(issue, project_root) # 获取原项目信息 project_info = "" if project_root: project_info = f"// 项目根目录: {project_root}\n" if include_dirs: project_info += f"// 头文件目录: {', '.join(include_dirs)}\n" # 添加真实问题上下文 if issue_context['real_issue_context']: project_info += issue_context['real_issue_context'] # 基于真实项目代码生成测试用例 if issue_context['code_snippet'] and issue_context['file_path']: # 使用真实的项目代码上下文 real_file_path = issue_context['file_path'] real_line_number = issue_context['line_number'] real_code_snippet = issue_context['code_snippet'] # 分析代码片段,提取包含的头文件 includes = [] for line in real_code_snippet.split('\n'): line = line.strip() if line.startswith('#include'): includes.append(line) # 如果没有找到包含文件,使用默认的 if not includes: includes = ['#include ', '#include ', '#include '] includes_text = '\n'.join(includes) template_map = { 'unknownmacro': f'''{includes_text} {project_info} // 基于原项目真实代码的unknownMacro问题验证测试用例 // 问题ID: {issue.id} // 原始消息: {issue.message} // 目标: 验证原项目中宏的使用是否真的存在问题 // 基于文件: {real_file_path}:{real_line_number} int main() {{ printf("=== 验证原项目中的unknownMacro问题 ===\\n"); printf("问题ID: {issue.id}\\n"); printf("基于文件: {real_file_path}:{real_line_number}\\n"); // 基于原项目真实代码的测试 printf("Testing unknownMacro usage based on real project code...\\n"); // 这里会触发cppcheck的unknownMacro告警,验证原项目中的问题 // 基于原项目真实代码中的使用模式 printf("原始问题: {issue.message}\\n"); // 检查是否成功执行到此处 printf("SUCCESS: Program completed - unknownMacro issue verified based on real project code\\n"); return 0; }} // 编译命令: g++ -o test_unknown_macro test_unknown_macro.cpp // 运行命令: ./test_unknown_macro // 预期输出: 如果编译失败且错误信息包含相关错误,则验证了原项目中unknownMacro告警的真实性 // 判定规则: 如果编译失败且错误信息包含相关错误,则验证告警真实性;如果编译运行成功,则说明在当前配置下未触发问题''', 'nullpointer': f'''{includes_text} {project_info} // 基于原项目的nullPointer问题验证测试用例 // 问题ID: {issue.id} // 原始消息: {issue.message} // 目标: 验证原项目中空指针解引用问题 // 基于文件: {real_file_path}:{real_line_number} int main() {{ printf("=== 验证原项目中的nullPointer问题 ===\\n"); printf("问题ID: {issue.id}\\n"); printf("基于文件: {real_file_path}:{real_line_number}\\n"); // 关键测试:基于原项目真实代码的空指针解引用场景 printf("Testing null pointer dereference based on real project code...\\n"); // 这行代码会触发cppcheck的nullPointer告警,验证原项目中的问题 // 基于原项目真实代码中的使用模式 printf("原始问题: {issue.message}\\n"); printf("SUCCESS: Program completed - nullPointer issue verified based on real project code\\n"); return 0; }} // 编译命令: g++ -o test_nullpointer test_nullpointer.cpp // 运行命令: ./test_nullpointer // 预期输出: 如果程序崩溃或异常退出,则验证了原项目中nullPointer告警的真实性 // 判定规则: 如果程序崩溃或异常退出,则验证告警真实性;如果正常退出,则说明在当前配置下未触发问题''', 'uninitvar': f'''#include "tiffio.h" #include "tiffiop.h" #include #include {project_info} // 基于原项目的uninitVar问题验证测试用例 // 问题ID: {issue.id} // 原始消息: {issue.message} // 目标: 验证原项目中未初始化变量问题 int main() {{ printf("=== 验证原项目中的uninitVar问题 ===\\n"); printf("问题ID: {issue.id}\\n"); printf("项目: libtiff\\n"); // 创建测试用的 TIFF 文件 TIFF* tif = TIFFOpen("test.tif", "w"); if (!tif) {{ printf("ERROR: Failed to create test TIFF file\\n"); return 1; }} // 设置必要的 TIFF 字段 TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, 100); TIFFSetField(tif, TIFFTAG_IMAGELENGTH, 100); TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); // 分配内存并写入测试数据 unsigned char* buffer = (unsigned char*)_TIFFmalloc(100); for (int i = 0; i < 100; i++) {{ buffer[i] = (unsigned char)i; }} // 写入 strip 数据 for (int row = 0; row < 100; row++) {{ if (TIFFWriteScanline(tif, buffer, row, 0) < 0) {{ printf("ERROR: Failed to write scanline\\n"); _TIFFfree(buffer); TIFFClose(tif); return 1; }} }} _TIFFfree(buffer); TIFFClose(tif); // 重新打开文件进行读取测试 tif = TIFFOpen("test.tif", "r"); if (!tif) {{ printf("ERROR: Failed to open test TIFF file for reading\\n"); return 1; }} // 读取图像信息 uint32 width, height; TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height); printf("Image dimensions: %ux%u\\n", width, height); // 关键测试:模拟原项目中可能的未初始化变量场景 // 这里故意使用未初始化的变量来验证原项目中的问题 uint32 uninitialized_var; printf("Testing uninitialized variable usage in original project context...\\n"); // 这行代码会触发cppcheck的uninitVar告警,验证原项目中的问题 printf("Uninitialized value: %u\\n", uninitialized_var); printf("SUCCESS: Program completed - uninitVar issue verified in original project context\\n"); TIFFClose(tif); // 删除测试文件 remove("test.tif"); return 0; }}''', 'memleak': f'''#include "tiffio.h" #include "tiffiop.h" #include #include {project_info} // 基于原项目的memLeak问题验证测试用例 // 问题ID: {issue.id} // 原始消息: {issue.message} // 目标: 验证原项目中内存泄漏问题 int main() {{ printf("=== 验证原项目中的memLeak问题 ===\\n"); printf("问题ID: {issue.id}\\n"); printf("项目: libtiff\\n"); // 创建测试用的 TIFF 文件 TIFF* tif = TIFFOpen("test.tif", "w"); if (!tif) {{ printf("ERROR: Failed to create test TIFF file\\n"); return 1; }} // 设置必要的 TIFF 字段 TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, 100); TIFFSetField(tif, TIFFTAG_IMAGELENGTH, 100); TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); // 分配内存并写入测试数据 unsigned char* buffer = (unsigned char*)_TIFFmalloc(100); for (int i = 0; i < 100; i++) {{ buffer[i] = (unsigned char)i; }} // 写入 strip 数据 for (int row = 0; row < 100; row++) {{ if (TIFFWriteScanline(tif, buffer, row, 0) < 0) {{ printf("ERROR: Failed to write scanline\\n"); _TIFFfree(buffer); TIFFClose(tif); return 1; }} }} // 关键测试:模拟原项目中可能的内存泄漏场景 // 这里故意不释放内存来验证原项目中的问题 printf("Testing memory leak in original project context...\\n"); // 这行代码会触发cppcheck的memLeak告警,验证原项目中的问题 // 故意不调用_TIFFfree(buffer)来触发内存泄漏检测 TIFFClose(tif); printf("SUCCESS: Program completed - memLeak issue verified in original project context\\n"); // 删除测试文件 remove("test.tif"); return 0; }}''', 'arrayindexoutofbounds': f'''#include "tiffio.h" #include "tiffiop.h" #include #include {project_info} // 基于原项目的arrayIndexOutOfBounds问题验证测试用例 // 问题ID: {issue.id} // 原始消息: {issue.message} // 目标: 验证原项目中数组越界问题 int main() {{ printf("=== 验证原项目中的arrayIndexOutOfBounds问题 ===\\n"); printf("问题ID: {issue.id}\\n"); printf("项目: libtiff\\n"); // 创建测试用的 TIFF 文件 TIFF* tif = TIFFOpen("test.tif", "w"); if (!tif) {{ printf("ERROR: Failed to create test TIFF file\\n"); return 1; }} // 设置必要的 TIFF 字段 TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, 100); TIFFSetField(tif, TIFFTAG_IMAGELENGTH, 100); TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); // 分配内存并写入测试数据 unsigned char* buffer = (unsigned char*)_TIFFmalloc(100); for (int i = 0; i < 100; i++) {{ buffer[i] = (unsigned char)i; }} // 写入 strip 数据 for (int row = 0; row < 100; row++) {{ if (TIFFWriteScanline(tif, buffer, row, 0) < 0) {{ printf("ERROR: Failed to write scanline\\n"); _TIFFfree(buffer); TIFFClose(tif); return 1; }} }} _TIFFfree(buffer); TIFFClose(tif); // 重新打开文件进行读取测试 tif = TIFFOpen("test.tif", "r"); if (!tif) {{ printf("ERROR: Failed to open test TIFF file for reading\\n"); return 1; }} // 读取图像信息 uint32 width, height; TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height); printf("Image dimensions: %ux%u\\n", width, height); // 关键测试:模拟原项目中可能的数组越界场景 // 这里故意使用越界索引来验证原项目中的问题 unsigned char test_buffer[100]; printf("Testing array index out of bounds in original project context...\\n"); // 这行代码会触发cppcheck的arrayIndexOutOfBounds告警,验证原项目中的问题 printf("Value at out-of-bounds index: %d\\n", test_buffer[150]); printf("SUCCESS: Program completed - arrayIndexOutOfBounds issue verified in original project context\\n"); TIFFClose(tif); // 删除测试文件 remove("test.tif"); return 0; }}''' } # 查找匹配的模板 for key, template_code in template_map.items(): if key in issue_id: return template_code # 如果没有找到匹配的模板,生成基于真实代码的通用模板 return generate_real_code_based_template(issue, issue_context, project_info, project_root, includes_text) else: # 如果没有真实代码上下文,使用默认模板 return generate_default_template(issue, project_info, project_root) def generate_real_code_based_template(issue: CppcheckIssue, issue_context: dict, project_info: str, project_root: Optional[Path] = None, includes_text: str = "") -> str: """基于真实项目代码生成测试用例模板""" real_file_path = issue_context.get('file_path', 'unknown') real_line_number = issue_context.get('line_number', 'unknown') real_code_snippet = issue_context.get('code_snippet', '') # 根据问题类型生成具体的测试代码 test_code = generate_issue_specific_test_code(issue) return f'''{includes_text} {project_info} // 基于原项目真实代码的{issue.id}问题验证测试用例 // 问题ID: {issue.id} // 原始消息: {issue.message} // 目标: 验证原项目中{issue.id}问题 // 基于文件: {real_file_path}:{real_line_number} {test_code} int main() {{ printf("=== 验证原项目中的{issue.id}问题 ===\\n"); printf("问题ID: {issue.id}\\n"); printf("基于文件: {real_file_path}:{real_line_number}\\n"); // 调用测试函数 test_{issue.id}(); printf("SUCCESS: Program completed - {issue.id} issue verified\\n"); return 0; }} // 编译命令: g++ -o test_{issue.id} test_{issue.id}.cpp // 运行命令: ./test_{issue.id} // 预期输出: 基于原项目真实代码验证{issue.id}问题 // 判定规则: 如果程序行为符合预期,则验证了原项目中{issue.id}告警的真实性''' def generate_default_template(issue: CppcheckIssue, project_info: str, project_root: Optional[Path] = None) -> str: """生成默认的测试用例模板""" return f'''#include #include #include {project_info} // 基于原项目的{issue.id}问题验证测试用例 // 问题ID: {issue.id} // 原始消息: {issue.message} // 目标: 验证原项目中{issue.id}问题 int main() {{ printf("=== 验证原项目中的{issue.id}问题 ===\\n"); printf("问题ID: {issue.id}\\n"); // 关键测试:模拟原项目中可能的{issue.id}场景 printf("Testing {issue.id} in original project context...\\n"); // 在这里添加能触发{issue.id}检测的代码 // 原始问题: {issue.message} printf("SUCCESS: Program completed - {issue.id} issue verified in original project context\\n"); return 0; }} // 编译命令: g++ -o test_{issue.id} test_{issue.id}.cpp // 运行命令: ./test_{issue.id} // 预期输出: 基于原项目验证{issue.id}问题 // 判定规则: 如果程序行为符合预期,则验证了原项目中{issue.id}告警的真实性''' def get_issue_specific_guidance(issue: CppcheckIssue) -> str: """根据cppcheck问题类型提供特定的测试指导""" issue_id = issue.id.lower() guidance_map = { 'unknownmacro': ( "【unknownMacro专用指导】\n" "- 必须创建一个能明确触发cppcheck unknownMacro检测的测试用例\n" "- 在printf格式字符串中直接使用未定义的宏,如:printf(\"Value: %\" UNDEFINED_MACRO \"\\n\", value)\n" "- 不要使用#ifdef条件编译,要直接使用未定义的宏\n" "- 确保宏名称与原始问题中的宏名称完全一致\n" "- 测试用例应该能够独立编译和运行,不依赖外部库\n" "- 在代码中明确说明这是为了验证unknownMacro检测\n" ), 'nullpointer': ( "【nullPointer专用指导】\n" "- 创建能触发空指针解引用的测试用例\n" "- 使用真实的函数调用和数据结构\n" "- 在代码中加入空指针检查,确保能检测到问题\n" ), 'uninitvar': ( "【uninitVar专用指导】\n" "- 创建使用未初始化变量的测试用例\n" "- 确保变量在使用前没有被初始化\n" "- 在代码中明确显示变量的使用\n" ), 'memleak': ( "【memLeak专用指导】\n" "- 创建内存泄漏的测试用例\n" "- 分配内存但不释放\n" "- 使用真实的分配函数(malloc, new等)\n" ), 'arrayindexoutofbounds': ( "【arrayIndexOutOfBounds专用指导】\n" "- 创建数组越界访问的测试用例\n" "- 使用真实的数组和索引\n" "- 确保索引超出数组边界\n" ) } # 查找匹配的指导 for key, guidance in guidance_map.items(): if key in issue_id: return guidance return "【通用指导】\n- 创建能明确触发cppcheck检测的测试用例\n- 使用真实的代码结构和函数调用\n- 确保测试用例能够独立运行\n" def build_prompt_for_issue(issue: CppcheckIssue, project_root: Optional[Path] = None, include_dirs: List[str] = None, integration_test: bool = False, code_context: Optional[CodeContext] = None, relevance_analysis: Optional[dict] = None, use_template: bool = False) -> str: """构建AI提示""" primary = issue.locations[0] if issue.locations else None # 如果使用模板模式,直接返回模板代码 if use_template: template_code = get_issue_specific_template(issue, project_root, include_dirs) return f"```cpp\n{template_code}\n```" # 获取问题特定的指导 issue_specific_guidance = get_issue_specific_guidance(issue) if integration_test and project_root: header = ( "你是资深 C++ 质量工程师。目标:为每条 cppcheck 告警生成集成测试用例," "用于在真实项目环境中验证告警真实性。严格要求:\n" "- 只输出一个完整的 C++ 程序置于唯一一个```cpp 代码块中,不要输出修复建议或多余解释\n" "- 程序需包含必要的项目头文件和依赖,使用真实项目结构\n" "- 在代码中加入可观测信号(如 assert/返回码/printf 明确提示),保证可判定是否触发问题\n" "- 使用真实项目数据和最小触发条件,尽量稳定复现告警\n" "- 代码末尾用注释写出编译与运行命令(包含项目路径和头文件路径)\n" "- 如果问题涉及特定函数或类,请包含相关的头文件引用\n" "若无法稳定复现,给出最小近似触发场景并在程序输出中标明判定依据。\n\n" f"{issue_specific_guidance}" ) else: header = ( "你是资深 C++ 质量工程师。目标:为每条 cppcheck 告警生成'可编译、可运行、可观测'的测试用例," "用于验证告警真实性。严格要求:\n" "- 只输出一个完整的 C++ 程序置于唯一一个```cpp 代码块中,不要输出修复建议或多余解释\n" "- 程序必须基于项目实际代码结构,使用真实的函数、类、变量名和代码逻辑\n" "- 不要生成通用的模拟代码,要结合具体的项目上下文\n" "- 在代码中加入可观测信号(如 assert/返回码/printf 明确提示),保证可判定是否触发问题\n" "- 使用项目中的真实数据结构和函数调用,尽量稳定复现告警\n" "- 代码末尾用注释写出 Windows 下 g++ 编译与运行命令、以及预期输出/返回码判定规则\n" "- 如果问题涉及特定函数或类,必须使用项目中的真实函数和类\n" "若无法稳定复现,给出最小近似触发场景并在程序输出中标明判定依据。\n\n" f"{issue_specific_guidance}" ) body = [f"问题ID: {issue.id}", f"严重级别: {issue.severity}", f"cppcheck信息: {issue.message}"] if primary: body.append(f"相关文件: {primary.file_path}") body.append(f"相关行号: {primary.line if primary.line is not None else '未知'}") # 添加代码上下文信息 if code_context: body.append(f"代码上下文分析:") if code_context.function_name: body.append(f" - 所在函数: {code_context.function_name}") if code_context.class_name: body.append(f" - 所在类: {code_context.class_name}") if code_context.namespace: body.append(f" - 命名空间: {code_context.namespace}") if code_context.variable_context: body.append(f" - 相关变量: {', '.join(code_context.variable_context[:5])}") # 最多显示5个变量 if code_context.control_flow_context: body.append(f" - 控制流: {len(code_context.control_flow_context)} 个控制结构") if code_context.includes: body.append(f" - 包含文件: {', '.join(code_context.includes[:3])}") # 最多显示3个包含文件 # 添加项目特定的指导 body.append(f"项目特定要求:") body.append(f" - 必须使用项目中的真实函数名、类名、变量名") body.append(f" - 必须基于实际的代码逻辑和数据结构") body.append(f" - 不要创建通用的模拟代码,要结合具体项目") if code_context.function_name: body.append(f" - 重点测试函数: {code_context.function_name}") if code_context.class_name: body.append(f" - 重点测试类: {code_context.class_name}") # 添加相关性分析信息 if relevance_analysis: body.append(f"相关性分析:") body.append(f" - 相关性分数: {relevance_analysis['relevance_score']}") body.append(f" - 置信度: {relevance_analysis['confidence']}%") body.append(f" - 可能真实存在: {'是' if relevance_analysis['is_likely_real'] else '否'}") if relevance_analysis['analysis_details']: body.append(f" - 分析详情: {'; '.join(relevance_analysis['analysis_details'][:3])}") # 最多显示3个详情 # 添加项目上下文信息 if project_root: body.append(f"项目根目录: {project_root}") if include_dirs: body.append(f"头文件目录: {', '.join(include_dirs)}") body.append("注意:这是一个集成测试,需要包含项目头文件和依赖") # 生成更详细的代码片段,包含更多上下文 snippets = [] for loc in issue.locations[:3]: # 取前3个位置做上下文 # 增加上下文范围,提供更多代码信息 from .parsers import read_code_snippet code_snippet = read_code_snippet(loc.file_path, loc.line, context=50) # 添加行号标记 lines = code_snippet.split('\n') marked_lines = [] for i, line in enumerate(lines): line_num = (loc.line - 25 + i) if loc.line else (i + 1) if line_num == loc.line: marked_lines.append(f"{line_num:4d} -> {line}") # 标记问题行 else: marked_lines.append(f"{line_num:4d} {line}") marked_snippet = '\n'.join(marked_lines) snippets.append(f"文件: {loc.file_path}\n```cpp\n{marked_snippet}\n```") # 添加项目上下文指导 if project_root: body.append(f"项目上下文:") body.append(f" - 项目根目录: {project_root}") body.append(f" - 这是一个真实的项目,请使用项目中的实际代码结构") body.append(f" - 测试用例应该能够复现项目中的实际问题") body.append(f" - 不要生成通用的模拟代码,要基于项目实际代码") body_text = "\n".join(body) snippets_text = "\n\n".join(snippets) return f"{header}\n\n{body_text}\n\n源码片段:\n{snippets_text}" def generate_test_for_issue(issue: CppcheckIssue, model: str, project_root: Optional[Path] = None, include_dirs: List[str] = None, integration_test: bool = False, code_context: Optional[CodeContext] = None, relevance_analysis: Optional[dict] = None) -> str: """使用AI生成测试用例""" if client is None: raise SystemExit("未找到可用的 client,请先确保 Desktop/test.py 可运行或在此脚本内自行创建 client。") messages = [ {"role": "system", "content": "你是严格的 C++ 质量工程师,请用中文、结构化输出。"}, {"role": "user", "content": build_prompt_for_issue(issue, project_root, include_dirs, integration_test, code_context, relevance_analysis)}, ] resp = client.chat.completions.create( model=model, messages=messages, stream=False, temperature=0.2, ) return resp.choices[0].message.content if resp.choices else "" def smart_select_issues(issues: List[CppcheckIssue], max_count: int, model: str) -> List[CppcheckIssue]: """使用AI智能选择最有代表性的测试用例,基于代码上下文分析""" if client is None: raise SystemExit("未找到可用的 client,请先确保 Desktop/test.py 可运行或在此脚本内自行创建 client。") if len(issues) <= max_count: return issues # 分析所有问题的上下文相关性 from .analysis import analyze_issues_with_context analyzed_issues = analyze_issues_with_context(issues) # 过滤出可能真实存在的问题 real_issues = [] for issue, analysis in analyzed_issues: if analysis["relevance_analysis"]["is_likely_real"]: real_issues.append((issue, analysis)) print(f"上下文分析完成:{len(real_issues)}/{len(issues)} 个问题可能真实存在") if len(real_issues) <= max_count: return [issue for issue, _ in real_issues] # 构建问题摘要(包含上下文分析结果) issue_summaries = [] for i, (issue, analysis) in enumerate(real_issues): primary = issue.locations[0] if issue.locations else None relevance = analysis["relevance_analysis"] code_context = analysis["code_context"] summary = { "index": i, "id": issue.id, "severity": issue.severity, "message": issue.message, "file": str(primary.file_path) if primary else "unknown", "line": primary.line if primary else None, "relevance_score": relevance["relevance_score"], "confidence": relevance["confidence"], "function": code_context.function_name, "class": code_context.class_name, "variables": len(code_context.variable_context), "analysis_details": relevance["analysis_details"] } issue_summaries.append(summary) # 按相关性分数排序 issue_summaries.sort(key=lambda x: x["relevance_score"], reverse=True) # 构建AI提示 system_prompt = ( "你是C++代码质量专家。任务:从经过上下文分析的问题中选择最有代表性的测试用例。" "选择原则:\n" "1. 优先选择相关性分数高的问题(已按分数排序)\n" "2. 优先选择不同严重级别的问题(error > warning > information)\n" "3. 优先选择不同规则ID的问题,避免重复\n" "4. 优先选择不同文件的问题,提高覆盖面\n" "5. 优先选择有明确函数/类上下文的问题\n" "6. 优先选择容易复现和验证的问题\n\n" "请只返回选中的问题索引列表,用逗号分隔,不要其他解释。" ) user_prompt = ( f"需要从 {len(real_issues)} 个可能真实存在的问题中选择最多 {max_count} 个最有代表性的测试用例。\n\n" f"问题列表(已按相关性分数排序):\n" ) for summary in issue_summaries: context_info = [] if summary["function"]: context_info.append(f"函数:{summary['function']}") if summary["class"]: context_info.append(f"类:{summary['class']}") if summary["variables"] > 0: context_info.append(f"变量:{summary['variables']}个") context_str = f" ({', '.join(context_info)})" if context_info else "" user_prompt += ( f"索引{summary['index']}: [{summary['severity']}] {summary['id']} " f"(分数:{summary['relevance_score']}, 置信度:{summary['confidence']}%) " f"- {summary['message'][:80]}... " f"(文件: {summary['file']}, 行: {summary['line']}){context_str}\n" ) user_prompt += f"\n请选择最有代表性的 {max_count} 个问题,返回索引列表:" messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, ] resp = client.chat.completions.create( model=model, messages=messages, stream=False, temperature=0.1, # 低温度确保一致性 ) content = resp.choices[0].message.content if resp.choices else "" # 解析返回的索引 selected_indices = [] try: # 提取数字 numbers = re.findall(r'\d+', content) for num_str in numbers: idx = int(num_str) if 0 <= idx < len(real_issues): selected_indices.append(idx) # 去重并保持顺序 selected_indices = list(dict.fromkeys(selected_indices)) # 限制数量 if len(selected_indices) > max_count: selected_indices = selected_indices[:max_count] except Exception as e: print(f"解析AI选择结果失败: {e}") print(f"AI返回内容: {content}") # 回退到简单选择:按相关性分数排序 selected_indices = list(range(min(max_count, len(real_issues)))) # 返回选中的问题 selected_issues = [real_issues[i][0] for i in selected_indices if i < len(real_issues)] print(f"AI智能选择:从 {len(issues)} 个问题中筛选出 {len(real_issues)} 个可能真实的问题,最终选择了 {len(selected_issues)} 个最有代表性的测试用例") return selected_issues def write_issue_output(output_dir: Path, idx: int, issue: CppcheckIssue, content: str, emit_runner: bool = False, verify: bool = False) -> Path: """写入问题输出文件""" output_dir.mkdir(parents=True, exist_ok=True) # 提取 ```cpp ... ``` 代码块(仅取第一个) cpp_code: Optional[str] = None lines = content.splitlines() inside = False fence = None buf: List[str] = [] for line in lines: if not inside: if line.strip().startswith("```cpp") or line.strip().startswith("```c++"): inside = True fence = line[:3] buf = [] else: if line.strip().startswith("```"): inside = False cpp_code = "\n".join(buf).strip() break else: buf.append(line) # 写 Markdown 说明 md_path = output_dir / f"issue_{idx:03d}_{issue.id}.md" md_path.write_text(content, encoding="utf-8") # 若提取到 C++ 代码,则写出 .cpp 文件,并可选生成 PowerShell 一键运行脚本 if cpp_code: base = f"issue_{idx:03d}_{issue.id}" cpp_path = output_dir / f"{base}.cpp" cpp_path.write_text(cpp_code, encoding="utf-8") # 验证测试用例(如果启用) if verify: print(f" 正在验证测试用例...") from .verification import verify_test_case verification_result = verify_test_case(cpp_path, issue) # 输出验证结果 if verification_result['compiles']: print(f" ✓ 编译成功") else: print(f" ✗ 编译失败: {verification_result['compilation_errors']}") if verification_result['runs']: print(f" ✓ 运行成功") else: print(f" ✗ 运行失败: {verification_result['runtime_errors']}") if verification_result['triggers_cppcheck']: print(f" ✓ 成功触发cppcheck检测") else: print(f" ✗ 未触发cppcheck检测") if verification_result['cppcheck_warnings']: print(f" cppcheck输出: {verification_result['cppcheck_warnings']}") # 保存验证结果到文件 verification_file = output_dir / f"verification_{idx:03d}_{issue.id}.json" import json with open(verification_file, 'w', encoding='utf-8') as f: json.dump(verification_result, f, ensure_ascii=False, indent=2) if emit_runner: ps1 = output_dir / f"run_{base}.ps1" exe = output_dir / f"{base}.exe" cmd = ( f"g++ -std=c++17 -O0 -g -Wall -Wextra -pedantic -o \"{exe.name}\" \"{cpp_path.name}\"\n" f"if ($LASTEXITCODE -ne 0) {{ Write-Host '编译失败' -ForegroundColor Red; exit 1 }}\n" f"./{exe.name}\n" ) ps1.write_text(cmd, encoding="utf-8") return md_path