Files
codex/codex-rs/git-utils/src/baseline_recovery_tests.rs
T
dhruvgupta-oai 333d7bde0f Bound memory baseline files and recover incomplete snapshots (#57240)
## Why

Memory baseline snapshots need bounded file reads, and recovery must keep existing memory files visible to consolidation rather than treating them as already processed.

## What changed

- Limit baseline files to 10 MiB, skip oversized working files, and check stored blob sizes before reading them.
- Version baselines and replace missing, outdated, or unusable metadata with an empty baseline, preserving working files as additions. Also invalidate the baseline when a tracked file grows beyond the limit.
- Serialize baseline initialization, resets, and diffs with a cross-process lock. Isolate repository access and pin index writes to the baseline worktree.

## Testing

Add coverage for oversized files, legacy baseline migration, interrupted resets, cross-process locking, and consolidation with unchanged inputs after baseline recovery.

GitOrigin-RevId: 34ecc9e5700a88ed37eff83a90f08d8a9b0d9f29
2026-10-26 20:40:53 +00:00

120 lines
4.2 KiB
Rust

//! Exercises cross-process serialization and retrying an interrupted baseline reset.
use super::baseline_control;
use super::diff_since_latest_init;
use super::ensure_git_baseline_repository;
use super::reset_git_repository;
use super::tests::git_stdout;
use super::with_baseline_lock;
use anyhow::Result;
use pretty_assertions::assert_eq;
use std::fs;
use std::io;
use std::io::BufRead;
use std::io::Read;
use std::io::Write;
use std::path::Path;
use std::process::Stdio;
use std::time::Duration;
#[tokio::test]
async fn concurrent_operations_share_the_workspace_lock() -> Result<()> {
const CHILD_ROOT: &str = "CODEX_TEST_BASELINE_LOCK_ROOT";
const READY: &str = "holding workspace lock";
if let Some(root) = std::env::var_os(CHILD_ROOT) {
return with_baseline_lock(Path::new(&root), |_| {
println!("{READY}");
io::stdout().flush()?;
io::stdin().read_exact(&mut [0])?;
Ok(())
})
.await;
}
let workspace = tempfile::tempdir()?;
let root = workspace.path();
fs::write(root.join("MEMORY.md"), "original\n")?;
reset_git_repository(root).await?;
fs::write(root.join("MEMORY.md"), "updated\n")?;
let mut child = codex_utils_process::background_command(std::env::current_exe()?)
.args([
"--exact",
"baseline::recovery_tests::concurrent_operations_share_the_workspace_lock",
"--nocapture",
])
.env(CHILD_ROOT, root)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
let stdout = io::BufReader::new(child.stdout.as_mut().expect("piped stdout"));
assert!(
stdout
.lines()
.any(|line| line.is_ok_and(|line| line.contains(READY))),
"child exited before acquiring the lock"
);
let lock = fs::File::options()
.read(true)
.write(true)
.open(baseline_control(root).join("codex-baseline.lock"))?;
let contended = matches!(lock.try_lock(), Err(fs::TryLockError::WouldBlock));
drop(lock);
let reset = reset_git_repository(root);
let ensure = ensure_git_baseline_repository(root);
let diff = diff_since_latest_init(root);
tokio::pin!(reset, ensure, diff);
// Any one operation completing before the holder releases the lock is a regression.
let completed_while_locked = tokio::select! {
_ = &mut reset => Some("reset"),
_ = &mut ensure => Some("ensure"),
_ = &mut diff => Some("diff"),
() = tokio::time::sleep(Duration::from_millis(500)) => None,
};
child.stdin.take().expect("piped stdin").write_all(b"x")?;
assert!(child.wait()?.success(), "lock holder failed");
assert!(contended, "another process must own the lock");
assert_eq!(completed_while_locked, None);
let ((), (), diff) = tokio::try_join!(reset, ensure, diff)?;
if diff.has_changes() {
assert!(diff.unified_diff.contains("-original\n+updated"));
}
assert_eq!(git_stdout(root, &["show", "HEAD:MEMORY.md"]), "updated\n");
assert_eq!(git_stdout(root, &["show", ":MEMORY.md"]), "updated\n");
Ok(())
}
#[tokio::test]
async fn interrupted_reset_replays_working_files() -> Result<()> {
let workspace = tempfile::tempdir()?;
let root = workspace.path();
let memory = root.join("MEMORY.md");
fs::write(&memory, "original\n")?;
reset_git_repository(root).await?;
fs::write(&memory, "pending edit\n")?;
// Simulate exiting after committing the tree but before writing the index and version.
fs::remove_file(root.join(".git").join(super::BASELINE_VERSION_FILE))?;
fs::remove_file(root.join(".git/index"))?;
ensure_git_baseline_repository(root).await?;
ensure_git_baseline_repository(root).await?;
assert_eq!(
git_stdout(root, &["ls-tree", "-r", "--name-only", "HEAD"]),
""
);
assert_eq!(git_stdout(root, &["ls-files"]), "");
assert_eq!(fs::read_to_string(&memory)?, "pending edit\n");
let diff = diff_since_latest_init(root).await?;
assert_eq!(
diff.changes,
vec![super::GitBaselineChange {
status: super::GitBaselineChangeStatus::Added,
path: "MEMORY.md".to_string(),
}]
);
assert!(diff.unified_diff.contains("+pending edit"));
Ok(())
}