|
|
|
|
@ -0,0 +1,898 @@
|
|
|
|
|
"""
|
|
|
|
|
测试用例生成模块
|
|
|
|
|
"""
|
|
|
|
|
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 <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
|