mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
## 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
120 lines
4.2 KiB
Rust
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(())
|
|
}
|