Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/minimal-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ The script prints the temporary directory containing source copies, command logs

The regression script runs in Linux CI after the ordinary workspace tests. It uses the real analyzers, compiler, coverage tool, prompt builder, response parser, candidate checks, and repair machinery. Model replies are deterministic fixtures; these results do not measure LLM quality.

Generation, repair, and statistics no longer call the partial `target_clean` helper between candidates. Cargo reuses build artifacts as sources are replaced and restored; analysis still clears the check cache, and coverage retains its own profile cleanup. The task regression preserves the target directory across scenarios and checks compilation failures followed by successful repairs.

## Coverage Interpretation

`original/original-coverage.json` and `.xml` measure production code exercised by all original unit and integration tests through `utgen coverage`. Test bodies and helpers contribute to neither the coverage numerator nor denominator. The check requires both original `classify` branches to have executed. LLVM can emit separate records for the same branch location in different test binaries; the check unions those outcomes instead of requiring both in one record.
Expand Down Expand Up @@ -77,6 +79,6 @@ With four function workers and a two-attempt limit, both generation and repair m

## Answers Without Tests

Before this fix, a reply such as `fn helper() {}` parsed as Rust and let generation return success with zero candidate tests. Direct-test and prefix generation now require at least one extracted `#[test]` function and reuse the existing three-attempt format retry loop. This does not require assertions in a prefix or judge the semantic quality of a test.
Before this fix, a reply such as `fn helper() {}` parsed as Rust and let generation return success with zero candidate tests. Direct-test and prefix generation now require at least one extracted `#[test]` function and reuse the existing three-attempt format retry loop, with a fixed one-second wait before each retry and no wait before the first attempt. This does not require assertions in a prefix or judge the semantic quality of a test.

The task regression selects one function and exercises recovery after a helper-only reply, exhaustion after three such replies, and interruption by the shared request budget, in both generation paths. It compares saved raw answers with the server's replies, including code fences, verifies the accepted code is normalized, checks request/token counts, and requires failures to leave no successful candidate or statistics. The recovered tests must compile, pass and cover production code, including a prefix with no assertion before oracle generation. Unit tests additionally cover invalid Rust, imports/comments without tests, empty test modules, and existing helpers/test attributes. All fixtures use fresh generation directories and local HTTP; no full-project model trial is involved.
4 changes: 2 additions & 2 deletions docs/palm-rust-unit-test-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ utgen fix --project-dir <PROJECT_DIR> [--work-dir <WORK_DIR>...] [--tasks <N>]

`--requirement`、`--context` 和 `--oracle` 默认均关闭。即使不传 `--context`,当前生成器仍读取 focxt 索引和对应的上下文文件;该参数只控制是否把上下文加入提示词。

生成任务取得名额后才开始工作,将结果送入容量为 N 的队列后释放名额;串行消费者负责导入检查和候选编译。队列中的结果及正在验证的候选不占生成名额。修复任务则在该函数的修复结果保存后释放名额。源码插入、target 清理、编译或测试、诊断读取及源码恢复串行执行,模型请求可以重叠;该参数不改变 Cargo 内部的依赖编译并行度。同一工作副本不能供多个 PALM 进程同时使用,不同实验应使用独立副本和 target 目录。
生成任务取得名额后才开始工作,将结果送入容量为 N 的队列后释放名额;串行消费者负责导入检查和候选编译。队列中的结果及正在验证的候选不占生成名额。修复任务则在该函数的修复结果保存后释放名额。源码插入、编译或测试、诊断读取及源码恢复串行执行,模型请求可以重叠;该参数不改变 Cargo 内部的依赖编译并行度。生成、修复和统计流程已停用逐候选的 `target_clean`,由 Cargo 复用构建产物;分析阶段的完整清理和覆盖率工具自身的清理仍保留。同一工作副本不能供多个 PALM 进程同时使用,不同实验应使用独立副本和 target 目录。

任务异常会在等待所有任务结束后报告,并跳过后续覆盖率统计。修复成功时只删除本次创建的备份;任务失败时恢复源码并保留这些备份,已有备份不会被覆盖。候选本身编译不通过仍按测试结果处理。模型请求现在统一执行:`--request-timeout` 默认每次尝试 180 秒,包含响应体读取;网络错误、429(余额不足除外)和 5xx 最多尝试 3 次,其他 HTTP 错误或空回答直接报错。格式纠正和编译修复轮次单独计算,不再叠加网络重试。缺少 `usage` 的有效回答可以继续,但请求统计标记为不完整;生成和修复分别写入 `utgen/generation/gen-requests.json`、`fix-requests.json`。该期限不涵盖 Cargo 子进程,也不提供跨进程锁或强制终止恢复。

Expand Down Expand Up @@ -371,7 +371,7 @@ export PALM_CONFIG="$(pwd)/utgen/res/api.json"
- 提取 `use` 语句、`#[test]` 函数、测试模块、公共辅助代码。
- 要求 LLM 返回可被 `syn::File` 解析且至少包含一个当前支持的 `#[test]` 函数的 Rust 代码;语法错误与未提取到测试均返回明确原因。

完整测试和 prefix 生成共用这次解析与提取,失败时沿用最多 3 次的格式尝试,耗尽后报告生成失败并跳过后续统计。这里只判断是否存在测试候选;prefix 阶段允许没有断言,候选能否编译和通过由后续执行判断。每次收到的原始回答先保存在 `utgen/generation/answer/<encoded>/<chain>/test-attempt-N.txt` 或 `prefix-attempt-N.txt`,再移除代码围栏并解析;成功代码仍保存为 `code.rs` 或 `prefix.rs`。尝试文件路径会在后续命令中复用,不构成多轮实验历史;旧候选缓存也不会自动迁移,验收使用新的工作副本。
完整测试和 prefix 生成共用这次解析与提取,失败时沿用最多 3 次的格式尝试;首次尝试不等待,后续每次尝试前固定等待 1 秒,耗尽后报告生成失败并跳过后续统计。这里只判断是否存在测试候选;prefix 阶段允许没有断言,候选能否编译和通过由后续执行判断。每次收到的原始回答先保存在 `utgen/generation/answer/<encoded>/<chain>/test-attempt-N.txt` 或 `prefix-attempt-N.txt`,再移除代码围栏并解析;成功代码仍保存为 `code.rs` 或 `prefix.rs`。尝试文件路径会在后续命令中复用,不构成多轮实验历史;旧候选缓存也不会自动迁移,验收使用新的工作副本。

- `utgen/src/utils/insert.rs`
- 根据 `InsertKind` 把测试插入文件末尾或模块末尾。
Expand Down
2 changes: 1 addition & 1 deletion utgen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ utgen gen -p <target-crate-path> --requirement --context

Generation validates the branch index, context index, context files, and source paths before modifying the target. Missing or inconsistent artifacts are errors. A failed generation task is reported instead of being silently lost before statistics. `--tasks 0` is rejected.

Direct-test and prefix answers must parse as Rust and contain at least one supported `#[test]` function. Syntax errors and answers containing only helpers, imports, comments, or an empty test module use the existing format retry loop, with at most three attempts. Exhaustion fails that function's generation and skips subsequent command statistics. This checks candidate presence; compilation and execution still determine whether a candidate works. Prefixes are accepted without assertions because oracle generation follows separately.
Direct-test and prefix answers must parse as Rust and contain at least one supported `#[test]` function. Syntax errors and answers containing only helpers, imports, comments, or an empty test module use the existing format retry loop, with at most three attempts and a fixed one-second wait before each retry. The first attempt starts without a wait. Exhaustion fails that function's generation and skips subsequent command statistics. This checks candidate presence; compilation and execution still determine whether a candidate works. Prefixes are accepted without assertions because oracle generation follows separately.

Each answer returned by the request layer is saved before code-fence removal and parsing, under `utgen/generation/answer/<encoded>/<chain>/test-attempt-N.txt` or `prefix-attempt-N.txt`, with N starting at 1. Logs identify the stage, chain, attempt and rejection reason. Accepted code retains the existing `code.rs` / `prefix.rs` layout. These paths are reused across invocations, not an invocation history; use fresh copies for separate trials. Existing cached candidates, including old empty candidates, are still skipped and are not migrated by this validation change.

Expand Down
7 changes: 1 addition & 6 deletions utgen/src/gene/cot/prefix_gen.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::{LLM, Prompt, extract_test_functions};
use crate::types::{ChainTestAnswer, TestInfo};
use log::error;
use rand::Rng;
use std::collections::HashSet;
use std::fs;
use std::path::Path;
Expand All @@ -28,11 +27,7 @@ pub async fn gen_prefix(

for attempt in 1..=3 {
if attempt > 1 {
let random_secs = {
let mut rng = rand::rng();
rng.random_range(10..=30)
};
sleep(Duration::from_secs(random_secs)).await;
sleep(Duration::from_secs(1)).await;
}
let (answers, usage_completion, usage_prompt) =
match llm.fetch_answer(Some(system_pt), &user_pt, 1, false).await {
Expand Down
7 changes: 1 addition & 6 deletions utgen/src/gene/cot/test_gen.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::{LLM, Prompt, extract_test_functions};
use crate::types::{ChainTestAnswer, TestInfo};
use log::error;
use rand::Rng;
use std::collections::HashSet;
use std::fs;
use std::path::Path;
Expand All @@ -24,11 +23,7 @@ pub async fn gen_test(

for attempt in 1..=3 {
if attempt > 1 {
let random_secs = {
let mut rng = rand::rng();
rng.random_range(10..=30)
};
sleep(Duration::from_secs(random_secs)).await;
sleep(Duration::from_secs(1)).await;
}
let (answers, usage_completion, usage_prompt) =
match llm.fetch_answer(Some(system_pt), &user_pt, 1, false).await {
Expand Down
6 changes: 3 additions & 3 deletions utgen/src/gene/generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::{inputprompts, oracleprompts, prefixprompts, testprompts};
use crate::types::{BrData, ChainTestInfo, TestGenInfo};
use crate::utils::{
RestoreOnDrop, TemporaryFile, backup_file, cargo_check, delete_backup, insert_test,
restore_file, target_clean, use_check,
restore_file, use_check,
};
use log::info;
use std::collections::HashSet;
Expand Down Expand Up @@ -440,7 +440,7 @@ pub fn check_unit(test_gen: &mut TestGenInfo, project_dir: &Path, work_dir: &Pat
let pos = mod_code.len() - 1;
mod_code.splice(pos..pos, insert_code);
insert_test(insert_kind, &path, &mod_code);
let _ = target_clean(&work_dir);
// let _ = target_clean(&work_dir);
let result = cargo_check(&work_dir);
test_info.can_compile[num] = result;
// test.can_compile = false;
Expand Down Expand Up @@ -502,7 +502,7 @@ pub fn check_integration(test_gen: &mut TestGenInfo, work_dir: &Path) {
}
code += &(fn_code.join("\n") + "\n");
fs::write(&path, code).unwrap();
let _ = target_clean(&work_dir);
// let _ = target_clean(&work_dir);
let result = cargo_check(&work_dir);
test_info.can_compile[num] = result;
id += 1;
Expand Down
8 changes: 4 additions & 4 deletions utgen/src/run/llm_fix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
gene::LLM,
types::{InsertKind, TestGenInfo},
utils::{
RestoreOnDrop, cargo_check, create_backup, insert_test, restore_file, target_clean,
RestoreOnDrop, cargo_check, create_backup, insert_test, restore_file,
},
};
use log::{error, info, warn};
Expand Down Expand Up @@ -239,7 +239,7 @@ async fn compilation_fix_assistant_for_an_error(
restore_file(file_path);
let restore = RestoreOnDrop(file_path);
insert_test(insert_kind, Path::new(&file_path), &mod_code);
let _ = target_clean(&work_path);
// let _ = target_clean(&work_path);

let test_type = TestType::Error;
run_test(project_path, work_path, test_type, false, false);
Expand Down Expand Up @@ -344,7 +344,7 @@ async fn compilation_fix_assistant_for_one_fn(
restore_file(&file_path);
let restore = RestoreOnDrop(&file_path);
insert_test(insert_kind, Path::new(&file_path), &mod_code);
let _ = target_clean(&work_path);
// let _ = target_clean(&work_path);

let test_type = TestType::Error;
let test_name = format!("test_{}", fn_name);
Expand Down Expand Up @@ -421,7 +421,7 @@ async fn compilation_fix_assistant_for_one_fn(
insert_test(insert_kind, &file_path, &mod_code);
*test_code = test_file_content.codes;
chain_test.repaired[num] = true;
let _ = target_clean(&work_path);
// let _ = target_clean(&work_path);

chain_test.can_compile[num] = cargo_check(&work_path);
id += 1;
Expand Down
8 changes: 4 additions & 4 deletions utgen/src/run/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
FunctionSelection,
run::TIMEOUT_DERIVE,
types::TestGenInfo,
utils::{backup_file, delete_backup, insert_test, restore_file, target_clean},
utils::{backup_file, delete_backup, insert_test, restore_file},
run::run_all::{prepare_all_unit_tests_for_one_shot_run},
};
use log::{error, info};
Expand Down Expand Up @@ -317,7 +317,7 @@ pub fn gen_test_rate(
let mut test_rate_infos: Vec<TestRateInfo> = Vec::new();
let test_type = TestType::CoverageRate;
let start_time = SystemTime::now();
let _ = target_clean(work_dir);
// let _ = target_clean(work_dir);
let test_output = run_test(project_dir, work_dir, test_type, true, true);
let end_time = SystemTime::now();
let test_time = end_time.duration_since(start_time).unwrap().as_secs_f64();
Expand Down Expand Up @@ -415,7 +415,7 @@ pub fn gen_test_rate(
}
}
}
let _ = target_clean(work_dir);
// let _ = target_clean(work_dir);
let test_path = work_dir.join("tests");
let bak_test_path2 = work_dir.join("tests.bak2");
fs::rename(&test_path, &bak_test_path2).unwrap();
Expand Down Expand Up @@ -1431,7 +1431,7 @@ fn gen_coverage_rate_for_original_tests(
if exists(&bak_test_dir).unwrap() {
info!("Generate coverage rate for original tests");
fs::rename(&bak_test_dir, &test_dir).unwrap();
let _ = target_clean(work_dir);
// let _ = target_clean(work_dir);
let test_type = TestType::CoverageRate;
run_test(project_dir, work_dir, test_type, true, false);
for test_gen_info in test_gen_infos.iter() {
Expand Down
5 changes: 3 additions & 2 deletions utgen/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
mod backup;
mod check;
mod clean;
// Retain Cargo artifacts between candidates; keep the cleanup implementation for now.
// mod clean;
mod insert;
mod preprocess;

use crate::types::BrData;
pub use backup::*;
pub use check::*;
pub use clean::*;
// pub use clean::*;
pub use insert::insert_test;
pub use preprocess::*;
use std::{
Expand Down
Loading