You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

899 lines
36 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
测试用例生成模块
"""
import re
from pathlib import Path
from typing import List, Optional
from .models import CppcheckIssue, CodeContext
# 复用 test.py 中已配置好的 OpenAI clientDeepSeek
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 <iostream>', '#include <cstdlib>', '#include <cstdio>']
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 <stdio.h>
#include <assert.h>
{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 <stdio.h>
#include <assert.h>
{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 <stdio.h>
#include <assert.h>
{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 <iostream>
#include <cstdlib>
#include <cstdio>
{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