mirror of
https://github.com/openai/codex.git
synced 2026-04-24 22:54:54 +00:00
This PR introduces a `codex-utils-cargo-bin` utility crate that wraps/replaces our use of `assert_cmd::Command` and `escargot::CargoBuild`. As you can infer from the introduction of `buck_project_root()` in this PR, I am attempting to make it possible to build Codex under [Buck2](https://buck2.build) as well as `cargo`. With Buck2, I hope to achieve faster incremental local builds (largely due to Buck2's [dice](https://buck2.build/docs/insights_and_knowledge/modern_dice/) build strategy, as well as benefits from its local build daemon) as well as faster CI builds if we invest in remote execution and caching. See https://buck2.build/docs/getting_started/what_is_buck2/#why-use-buck2-key-advantages for more details about the performance advantages of Buck2. Buck2 enforces stronger requirements in terms of build and test isolation. It discourages assumptions about absolute paths (which is key to enabling remote execution). Because the `CARGO_BIN_EXE_*` environment variables that Cargo provides are absolute paths (which `assert_cmd::Command` reads), this is a problem for Buck2, which is why we need this `codex-utils-cargo-bin` utility. My WIP-Buck2 setup sets the `CARGO_BIN_EXE_*` environment variables passed to a `rust_test()` build rule as relative paths. `codex-utils-cargo-bin` will resolve these values to absolute paths, when necessary. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/8496). * #8498 * __->__ #8496
114 lines
3.3 KiB
Rust
114 lines
3.3 KiB
Rust
use pretty_assertions::assert_eq;
|
|
use std::collections::BTreeMap;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
use tempfile::tempdir;
|
|
|
|
#[test]
|
|
fn test_apply_patch_scenarios() -> anyhow::Result<()> {
|
|
for scenario in fs::read_dir("tests/fixtures/scenarios")? {
|
|
let scenario = scenario?;
|
|
let path = scenario.path();
|
|
if path.is_dir() {
|
|
run_apply_patch_scenario(&path)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Reads a scenario directory, copies the input files to a temporary directory, runs apply-patch,
|
|
/// and asserts that the final state matches the expected state exactly.
|
|
fn run_apply_patch_scenario(dir: &Path) -> anyhow::Result<()> {
|
|
let tmp = tempdir()?;
|
|
|
|
// Copy the input files to the temporary directory
|
|
let input_dir = dir.join("input");
|
|
if input_dir.is_dir() {
|
|
copy_dir_recursive(&input_dir, tmp.path())?;
|
|
}
|
|
|
|
// Read the patch.txt file
|
|
let patch = fs::read_to_string(dir.join("patch.txt"))?;
|
|
|
|
// Run apply_patch in the temporary directory. We intentionally do not assert
|
|
// on the exit status here; the scenarios are specified purely in terms of
|
|
// final filesystem state, which we compare below.
|
|
Command::new(codex_utils_cargo_bin::cargo_bin("apply_patch")?)
|
|
.arg(patch)
|
|
.current_dir(tmp.path())
|
|
.output()?;
|
|
|
|
// Assert that the final state matches the expected state exactly
|
|
let expected_dir = dir.join("expected");
|
|
let expected_snapshot = snapshot_dir(&expected_dir)?;
|
|
let actual_snapshot = snapshot_dir(tmp.path())?;
|
|
|
|
assert_eq!(
|
|
actual_snapshot,
|
|
expected_snapshot,
|
|
"Scenario {} did not match expected final state",
|
|
dir.display()
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
enum Entry {
|
|
File(Vec<u8>),
|
|
Dir,
|
|
}
|
|
|
|
fn snapshot_dir(root: &Path) -> anyhow::Result<BTreeMap<PathBuf, Entry>> {
|
|
let mut entries = BTreeMap::new();
|
|
if root.is_dir() {
|
|
snapshot_dir_recursive(root, root, &mut entries)?;
|
|
}
|
|
Ok(entries)
|
|
}
|
|
|
|
fn snapshot_dir_recursive(
|
|
base: &Path,
|
|
dir: &Path,
|
|
entries: &mut BTreeMap<PathBuf, Entry>,
|
|
) -> anyhow::Result<()> {
|
|
for entry in fs::read_dir(dir)? {
|
|
let entry = entry?;
|
|
let path = entry.path();
|
|
let Some(stripped) = path.strip_prefix(base).ok() else {
|
|
continue;
|
|
};
|
|
let rel = stripped.to_path_buf();
|
|
let file_type = entry.file_type()?;
|
|
if file_type.is_dir() {
|
|
entries.insert(rel.clone(), Entry::Dir);
|
|
snapshot_dir_recursive(base, &path, entries)?;
|
|
} else if file_type.is_file() {
|
|
let contents = fs::read(&path)?;
|
|
entries.insert(rel, Entry::File(contents));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()> {
|
|
for entry in fs::read_dir(src)? {
|
|
let entry = entry?;
|
|
let path = entry.path();
|
|
let file_type = entry.file_type()?;
|
|
let dest_path = dst.join(entry.file_name());
|
|
if file_type.is_dir() {
|
|
fs::create_dir_all(&dest_path)?;
|
|
copy_dir_recursive(&path, &dest_path)?;
|
|
} else if file_type.is_file() {
|
|
if let Some(parent) = dest_path.parent() {
|
|
fs::create_dir_all(parent)?;
|
|
}
|
|
fs::copy(&path, &dest_path)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|