utgen/ 是项目的 Python 主程序,负责加载静态分析结果、构造 Prompt、生成 GoogleTest,并按单个测试执行编译、运行、修复、筛选、覆盖率统计和导出。
项目使用 uv 正式管理 Python 版本和依赖:
.python-version固定 Python 3.12。pyproject.toml声明直接依赖。uv.lock锁定完整依赖图。- 根目录
.venv/是本机生成环境,不纳入版本控制。
在仓库根目录同步环境:
uv sync --frozen后续命令统一使用 uv run --frozen,无需手动激活虚拟环境,也避免本机索引配置改写锁文件。修改依赖时应更新 pyproject.toml 并重新生成 uv.lock,不要在文档中维护一套独立的手工安装列表。
运行完整流程还需要:
- 独立构建的
brinfo、focxt和include-finder。 - LLVM 17 系列的
libclang、clang-scan-deps和clangd-indexer。 - CMake、Ninja、GoogleTest 和 CTest。
- 覆盖率阶段所需的
gcov、lcov和genhtml。 - 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"配置项含义:
| 配置 | 说明 |
|---|---|
URL、API_KEY |
OpenAI 兼容聊天补全接口及鉴权信息。 |
MODEL |
聊天补全请求使用的模型名称。 |
LIBCLANG_PATH |
LLVM 17 的 libclang.so 或 libclang.dylib。 |
BRINFO_PATH、FOCXT_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_coverage、llm_tests、llm_tests_cxt、llm_tests_req、llm_tests_req_cxt 子目录,并把本目录的 CodeCoverage.cmake 放入目标项目的 cmake/。完整 CMake 示例见 运行说明。
在仓库根目录执行:
uv run --frozen python utgen/decldef.py \
-p /path/to/project \
-b /path/to/project/build该命令执行以下步骤:
- 使用
clang-scan-deps --format=experimental-full生成project_info/deps.json。 - 使用
clangd-indexer --executor=all-TUs --format=yaml生成project_info/decl_def.yaml。 - 解析声明、定义和翻译单元依赖,生成
project_info/deps_dict.json。
工具会校验外部命令返回码和输出结构,并以原子替换方式写入中间结果,失败时不会用空文件覆盖已有有效产物。--no-preprocess 仅跳过前两步,因此只应在 deps.json 和 decl_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主流程依次完成:
- 准备目标项目目录并刷新 CMake 配置。
- 运行
focxt项目分析和brinfo文件分析。 - 生成基础、上下文、需求、需求与上下文四类测试。
- 每生成一个测试,就立即编译;失败时调用编译修复。
- 编译通过后立即运行;断言、Mock、异常、崩溃和超时等失败进入测试修复。
- 候选修改必须重新通过编译和运行才会写回;失败候选会回滚。
- 全部用例处理后复验状态,只使用有效的
kept=true测试统计覆盖率并导出。
主要参数:
| 参数 | 说明 |
|---|---|
-t TYPE、--test-type TYPE |
选择 base、cxt、req 或 req-cxt;可重复指定,省略时处理全部四类。 |
--no-analysis |
跳过 focxt 和 brinfo,加载已有分析结果后重新生成测试。 |
--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_SKIP、DISABLED_、恒真断言、空 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=pass、run=pass、kept=true 且文件哈希一致的测试会连同 include.h 和 CMakeLists.txt 导出到 filtered_tests*。
完整目录结构、命名方式、普通断言、Mock、异常示例和保留条件见 单元测试用例格式规范。
llm_tests*/filter_manifest.json记录编译、运行、失败类型、修复轮数、日志、哈希、导出和kept状态。- LLM 原始响应、拆分测试和 manifest 使用临时文件加原子替换,降低中断导致半写文件的风险。
.generated仅在至少生成一个有效测试后创建。重新运行会跳过内容未变化且已经通过或已过滤的测试。- 提高
--max-repair-rounds后,因旧轮数上限耗尽而过滤的测试可以继续使用新增轮数。 - 函数目录已有
.generated但缺少include.h时,主流程会根据已加载函数上下文自动恢复,不会为此重新请求 LLM。 - 测试进程返回 0 仍不足以判定通过;本次 GoogleTest XML 中必须存在至少一个未禁用、未跳过且执行完成的测试。
uv run --frozen python -m unittest discover -s utgen/tests -p 'test_*.py'