Skip to content

Commit f005da4

Browse files
test: fix vacuous test in compile/common — extract atomic_write_parent_dir (#1565)
The previous test `atomic_write_parent_derivation_handles_bare_filename` duplicated the production-code logic inline instead of calling the real function. If `atomic_write_blocking` changed its parent-derivation expression, the test would still pass while the bug remained uncaught. Fix: extract lines 60-61 of `atomic_write_blocking` into a `pub(crate) fn atomic_write_parent_dir(path: &Path) -> PathBuf` helper; update the test to call the helper directly so regressions in the production logic are caught. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 65893c0 commit f005da4

1 file changed

Lines changed: 25 additions & 31 deletions

File tree

src/compile/common.rs

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use anyhow::{Context, Result};
44
use std::collections::{HashMap, HashSet};
5-
use std::path::Path;
5+
use std::path::{Path, PathBuf};
66

77
use super::extensions::{
88
CompilerExtension, Declarations, McpgConfig, McpgGatewayConfig, McpgServerConfig,
@@ -45,21 +45,26 @@ pub async fn atomic_write(path: &Path, contents: &str) -> Result<()> {
4545
.context("atomic_write task panicked")?
4646
}
4747

48+
/// Returns the directory in which the atomic tempfile should be created for a
49+
/// write to `path`. The tempfile must live on the same filesystem as `path`
50+
/// so that the final `persist()` rename is atomic (EXDEV guard).
51+
///
52+
/// - `path.parent() == Some(non-empty)` → use that parent directory.
53+
/// - `path.parent() == Some("")` (bare filename like `"agent.md"`) or `None`
54+
/// → use `"."` (current directory), which is on the same filesystem as a
55+
/// bare-filename destination.
56+
pub(crate) fn atomic_write_parent_dir(path: &Path) -> PathBuf {
57+
let parent = path.parent().filter(|p| !p.as_os_str().is_empty());
58+
parent
59+
.map(|p| p.to_owned())
60+
.unwrap_or_else(|| PathBuf::from("."))
61+
}
62+
4863
fn atomic_write_blocking(path: &Path, contents: &str) -> Result<()> {
4964
use std::io::Write;
5065

51-
// Determine the directory to create the tempfile in. We MUST use
52-
// a path on the same filesystem as the destination so that the
53-
// final `persist` rename is atomic (otherwise it fails with
54-
// EXDEV on Linux when /tmp is a separate tmpfs mount).
55-
//
56-
// - `path.parent() == Some(non-empty)` -> use that parent.
57-
// - `path.parent() == Some("")` (bare filename like "agent.md")
58-
// or `None` -> use the current directory ("."), which is the
59-
// same filesystem as where the file will land.
60-
let parent = path.parent().filter(|p| !p.as_os_str().is_empty());
61-
let parent_dir: &Path = parent.unwrap_or_else(|| Path::new("."));
62-
let mut tmp = tempfile::NamedTempFile::new_in(parent_dir).with_context(|| {
66+
let parent_dir = atomic_write_parent_dir(path);
67+
let mut tmp = tempfile::NamedTempFile::new_in(&parent_dir).with_context(|| {
6368
format!(
6469
"failed to create temporary file in {}",
6570
parent_dir.display()
@@ -3284,28 +3289,17 @@ mod tests {
32843289
}
32853290

32863291
#[test]
3287-
fn atomic_write_parent_derivation_handles_bare_filename() {
3292+
fn atomic_write_parent_dir_handles_bare_filename() {
32883293
// Regression test for the EXDEV bug: a bare filename (no
32893294
// directory component) used to fall through to
32903295
// `NamedTempFile::new()` which creates the tempfile in the
32913296
// system temp dir, breaking persist() across filesystem
3292-
// boundaries (e.g. tmpfs `/tmp` on Linux). Verify the
3293-
// derivation logic now picks ".". Pure-logic test — no
3294-
// filesystem I/O so it's parallel-safe.
3295-
let bare = Path::new("agent.md");
3296-
let parent = bare.parent().filter(|p| !p.as_os_str().is_empty());
3297-
let parent_dir: &Path = parent.unwrap_or_else(|| Path::new("."));
3298-
assert_eq!(parent_dir, Path::new("."));
3299-
3300-
let with_dir = Path::new("subdir/agent.md");
3301-
let parent = with_dir.parent().filter(|p| !p.as_os_str().is_empty());
3302-
let parent_dir: &Path = parent.unwrap_or_else(|| Path::new("."));
3303-
assert_eq!(parent_dir, Path::new("subdir"));
3304-
3305-
let absolute = Path::new("/tmp/agent.md");
3306-
let parent = absolute.parent().filter(|p| !p.as_os_str().is_empty());
3307-
let parent_dir: &Path = parent.unwrap_or_else(|| Path::new("."));
3308-
assert_eq!(parent_dir, Path::new("/tmp"));
3297+
// boundaries (e.g. tmpfs `/tmp` on Linux). Verify that the
3298+
// extracted helper returns "." for a bare filename so the
3299+
// tempfile lands on the same filesystem as the destination.
3300+
assert_eq!(atomic_write_parent_dir(Path::new("agent.md")), PathBuf::from("."));
3301+
assert_eq!(atomic_write_parent_dir(Path::new("subdir/agent.md")), PathBuf::from("subdir"));
3302+
assert_eq!(atomic_write_parent_dir(Path::new("/tmp/agent.md")), PathBuf::from("/tmp"));
33093303
}
33103304

33113305
// ─── parse_markdown_detailed ──────────────────────────────────────────────

0 commit comments

Comments
 (0)