Skip to content

Latest commit

 

History

History
231 lines (166 loc) · 9.72 KB

File metadata and controls

231 lines (166 loc) · 9.72 KB

UTGen

utgen/ 是项目的 Python 主程序,负责加载静态分析结果、构造 Prompt、生成 GoogleTest,并按单个测试执行编译、运行、修复、筛选、覆盖率统计和导出。

Python 环境

项目使用 uv 正式管理 Python 版本和依赖:

  • .python-version 固定 Python 3.12。
  • pyproject.toml 声明直接依赖。
  • uv.lock 锁定完整依赖图。
  • 根目录 .venv/ 是本机生成环境,不纳入版本控制。

在仓库根目录同步环境:

uv sync --frozen

后续命令统一使用 uv run --frozen,无需手动激活虚拟环境,也避免本机索引配置改写锁文件。修改依赖时应更新 pyproject.toml 并重新生成 uv.lock,不要在文档中维护一套独立的手工安装列表。

工具依赖

运行完整流程还需要:

  • 独立构建的 brinfofocxtinclude-finder
  • LLVM 17 系列的 libclangclang-scan-depsclangd-indexer
  • CMake、Ninja、GoogleTest 和 CTest。
  • 覆盖率阶段所需的 gcovlcovgenhtml
  • OpenAI 兼容的大模型接口。

三个静态分析工具的构建方法及 macOS 配置见 analysis-tools/README.md

为使测试命令失败后仍继续执行覆盖率采集,可把下面的 ctest.sh 放入 PATH 并授予执行权限:

#!/bin/sh
ctest "$@"
exit 0

本机配置

utgen/ 下创建 config_private.py。该文件已被 .gitignore 忽略,适合保存 API Key 和本机绝对路径:

import os

URL = "https://your-llm-endpoint/v1/chat/completions"
API_KEY = "your-api-key"
MODEL = "your-model-name"
LIBCLANG_PATH = "/path/to/libclang.so"  # macOS 使用 libclang.dylib
BRINFO_PATH = "/path/to/brinfo"
FOCXT_PATH = "/path/to/focxt"
CPU_COUNT = os.cpu_count() or 4
CLANG_SCAN_DEPS_PATH = "/path/to/clang-scan-deps"
CLANGD_INDEXER_PATH = "/path/to/clangd-indexer"

配置项含义:

配置 说明
URLAPI_KEY OpenAI 兼容聊天补全接口及鉴权信息。
MODEL 聊天补全请求使用的模型名称。
LIBCLANG_PATH LLVM 17 的 libclang.solibclang.dylib
BRINFO_PATHFOCXT_PATH 两个分析工具的可执行文件绝对路径。
CPU_COUNT 项目分析的最大并发基准,可按机器资源调整。
CLANG_SCAN_DEPS_PATH clang-scan-deps 可执行文件。
CLANGD_INDEXER_PATH clangd-indexer 可执行文件。

include-finder 没有单独配置项,主程序从 PATH 查找它。libclang 动态库、Python clang 绑定以及分析工具应保持在 LLVM 17 主版本。

准备目标项目

先为目标项目生成编译数据库:

cmake -S /path/to/project -B /path/to/project/build -G Ninja \
  -DCMAKE_BUILD_TYPE=Debug \
  -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

若项目以头文件为主,编译数据库没有覆盖待分析文件,可在项目根目录创建 file_list.json,填入绝对路径:

[
  "/path/to/project/include/a.hpp",
  "/path/to/project/include/b.hpp"
]

目标项目还应启用 CTest 和 GoogleTest,接入 llm_coveragellm_testsllm_tests_cxtllm_tests_reqllm_tests_req_cxt 子目录,并把本目录的 CodeCoverage.cmake 放入目标项目的 cmake/。完整 CMake 示例见 运行说明

生成跨文件依赖

在仓库根目录执行:

uv run --frozen python utgen/decldef.py \
  -p /path/to/project \
  -b /path/to/project/build

该命令执行以下步骤:

  1. 使用 clang-scan-deps --format=experimental-full 生成 project_info/deps.json
  2. 使用 clangd-indexer --executor=all-TUs --format=yaml 生成 project_info/decl_def.yaml
  3. 解析声明、定义和翻译单元依赖,生成 project_info/deps_dict.json

工具会校验外部命令返回码和输出结构,并以原子替换方式写入中间结果,失败时不会用空文件覆盖已有有效产物。--no-preprocess 仅跳过前两步,因此只应在 deps.jsondecl_def.yaml 已存在且仍有效时使用。

默认生成流程

uv run --frozen python utgen/main.py \
  -p /path/to/project \
  -b /path/to/project/build \
  --max-repair-rounds 10 \
  --repair-choices 3 \
  --test-timeout 600

主流程依次完成:

  1. 准备目标项目目录并刷新 CMake 配置。
  2. 运行 focxt 项目分析和 brinfo 文件分析。
  3. 生成基础、上下文、需求、需求与上下文四类测试。
  4. 每生成一个测试,就立即编译;失败时调用编译修复。
  5. 编译通过后立即运行;断言、Mock、异常、崩溃和超时等失败进入测试修复。
  6. 候选修改必须重新通过编译和运行才会写回;失败候选会回滚。
  7. 全部用例处理后复验状态,只使用有效的 kept=true 测试统计覆盖率并导出。

主要参数:

参数 说明
-t TYPE--test-type TYPE 选择 basecxtreqreq-cxt;可重复指定,省略时处理全部四类。
--no-analysis 跳过 focxtbrinfo,加载已有分析结果后重新生成测试。
--max-repair-rounds N 单测试编译修复和测试修复共享的总轮数,默认 10。
--repair-choices N 每轮请求返回的候选数,默认 3。
--test-timeout N 单测试运行超时秒数,默认 600。
--batch-workflow 使用“全部生成后统一检查”的历史流程。
-s-c-f 限定源文件、类和函数。

缺少编译命令或依赖目标构建失败不会消耗 LLM 修复轮数。每轮最多发起一次逻辑 LLM 请求,并按顺序验证返回候选。

分阶段处理

默认主流程已自动完成检查、修复、筛选、覆盖率和导出。处理旧产物或需要人工控制阶段时可使用:

# 对已有产物执行完整后处理,不重新生成测试
uv run --frozen python utgen/main.py -p /path/to/project -b /path/to/project/build --post-analysis

# 只做编译检查并更新 manifest
uv run --frozen python utgen/main.py -p /path/to/project -b /path/to/project/build --compile-only

# 只运行编译通过的测试并更新运行状态和覆盖率
uv run --frozen python utgen/main.py -p /path/to/project -b /path/to/project/build --test-only

# 只导出当前状态有效且 kept=true 的测试
uv run --frozen python utgen/main.py -p /path/to/project -b /path/to/project/build --export-only

这里的“筛选”或“剔除”不会删除 llm_tests* 中的原始测试,而是把失败测试在 filter_manifest.json 中标记为 kept=false。文件被人工修改后,manifest 中的旧 SHA-256 状态会失效,必须重新执行编译和运行检查才能导出。

独立修复脚本

独立脚本主要用于旧批处理产物或定向重试。默认在线流程通常不需要额外调用。

修复编译失败测试:

uv run --frozen python utgen/compile_repair.py \
  -p /path/to/project \
  -b /path/to/project/build \
  -t req-cxt

-t 使用与主入口相同的类型名称,可重复指定;省略时处理全部四类测试。脚本优先读取 manifest,只修复 compile=fail 的记录,并重新执行对应编译命令确认结果。目标项目需要支持编译诊断 JSON:

add_compile_options(-fdiagnostics-format=json-file)

修复编译通过但运行失败的测试:

uv run --frozen python utgen/test_repair.py \
  -p /path/to/project \
  -b /path/to/project/build \
  -t req-cxt \
  --max-rounds 3 \
  --choices 3 \
  --timeout 600

测试修复 Prompt 会提供失败类型、测试输出、当前测试、include.h、被测函数上下文和原始需求。模型可以修正断言、Mock 交互、输入与状态构造、异常预期及导致崩溃或超时的测试逻辑,但候选不得用 GTEST_SKIPDISABLED_、恒真断言、空 catch 或移除被测调用来绕过失败。

单元测试文件格式

生成测试保存在目标项目的 llm_tests* 中。即使被测文件是 C 源码,测试也统一使用 .cpp 和 GoogleTest;通常一个 test_*.cpp 对应一个主要测试:

#include "include.h"

TEST(TestSuiteName, TestName) {
    const auto actual = focal_function(/* valid inputs */);
    EXPECT_EQ(actual, expected_value);
}

*_req* 模式还会在 TEST 前保留输入、前置条件和预期结果注释。测试必须真实调用被测函数,并检查返回值、输出参数、状态、异常或 Mock 交互。最终满足 compile=passrun=passkept=true 且文件哈希一致的测试会连同 include.hCMakeLists.txt 导出到 filtered_tests*

完整目录结构、命名方式、普通断言、Mock、异常示例和保留条件见 单元测试用例格式规范

状态与恢复

  • llm_tests*/filter_manifest.json 记录编译、运行、失败类型、修复轮数、日志、哈希、导出和 kept 状态。
  • LLM 原始响应、拆分测试和 manifest 使用临时文件加原子替换,降低中断导致半写文件的风险。
  • .generated 仅在至少生成一个有效测试后创建。重新运行会跳过内容未变化且已经通过或已过滤的测试。
  • 提高 --max-repair-rounds 后,因旧轮数上限耗尽而过滤的测试可以继续使用新增轮数。
  • 函数目录已有 .generated 但缺少 include.h 时,主流程会根据已加载函数上下文自动恢复,不会为此重新请求 LLM。
  • 测试进程返回 0 仍不足以判定通过;本次 GoogleTest XML 中必须存在至少一个未禁用、未跳过且执行完成的测试。

测试 Python 代码

uv run --frozen python -m unittest discover -s utgen/tests -p 'test_*.py'