huskies: merge 1238 bug worktree tests fail on macOS: tests clobber global HOME

This commit is contained in:
Huskies Agent
2026-07-21 12:10:20 +00:00
parent 22710571df
commit 8a32c1ffd8
20 changed files with 488 additions and 248 deletions
+36 -25
View File
@@ -1161,34 +1161,45 @@ fn display_section_returns_closed_for_new_terminal_variants() {
/// Initialise a bare-minimum git repo in `dir` with one commit.
fn init_git_repo(dir: &std::path::Path) {
use crate::git_test_support::git_ok;
use std::process::Command;
Command::new("git")
.args(["init", "-b", "main"])
.current_dir(dir)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output()
.unwrap();
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output()
.unwrap();
git_ok(
Command::new("git")
.args(["init", "-b", "main"])
.current_dir(dir)
.output(),
"git init",
);
git_ok(
Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output(),
"git config user.email",
);
git_ok(
Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output(),
"git config user.name",
);
// Create an initial commit so the repo has a HEAD.
std::fs::write(dir.join("README.md"), "# test").unwrap();
Command::new("git")
.args(["add", "README.md"])
.current_dir(dir)
.output()
.unwrap();
Command::new("git")
.args(["commit", "-m", "init"])
.current_dir(dir)
.output()
.unwrap();
git_ok(
Command::new("git")
.args(["add", "README.md"])
.current_dir(dir)
.output(),
"git add",
);
git_ok(
Command::new("git")
.args(["commit", "-m", "init"])
.current_dir(dir)
.output(),
"git commit",
);
}
#[test]
+36 -25
View File
@@ -566,33 +566,44 @@ mod tests {
/// Initialise a bare-minimum git repo in `dir` with one commit.
fn init_git_repo(dir: &std::path::Path) {
use crate::git_test_support::git_ok;
use std::process::Command as Cmd;
Cmd::new("git")
.args(["init", "-b", "main"])
.current_dir(dir)
.output()
.unwrap();
Cmd::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output()
.unwrap();
Cmd::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output()
.unwrap();
git_ok(
Cmd::new("git")
.args(["init", "-b", "main"])
.current_dir(dir)
.output(),
"git init",
);
git_ok(
Cmd::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir)
.output(),
"git config user.email",
);
git_ok(
Cmd::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir)
.output(),
"git config user.name",
);
std::fs::write(dir.join("README.md"), "# test").unwrap();
Cmd::new("git")
.args(["add", "README.md"])
.current_dir(dir)
.output()
.unwrap();
Cmd::new("git")
.args(["commit", "-m", "init"])
.current_dir(dir)
.output()
.unwrap();
git_ok(
Cmd::new("git")
.args(["add", "README.md"])
.current_dir(dir)
.output(),
"git add",
);
git_ok(
Cmd::new("git")
.args(["commit", "-m", "init"])
.current_dir(dir)
.output(),
"git commit",
);
}
#[test]
+45
View File
@@ -10,10 +10,55 @@ use std::path::{Path, PathBuf};
/// replaced by `-` (e.g. `/workspace/.huskies/worktrees/1186` becomes
/// `-workspace--huskies-worktrees-1186`).
pub fn transcript_path(cwd: &Path, session_id: &str) -> PathBuf {
#[cfg(test)]
if let Some(home) = test_home::get() {
return transcript_path_under_home(&home, cwd, session_id);
}
let home = std::env::var("HOME").unwrap_or_else(|_| "/home/huskies".to_string());
transcript_path_under_home(Path::new(&home), cwd, session_id)
}
/// Per-thread `$HOME` override for tests, so a test can sandbox where
/// [`transcript_path`] looks without mutating the process-global `$HOME`
/// env var (which every thread shares, including unrelated `git`
/// subprocesses spawned by other tests reading `$HOME` for
/// `~/.gitconfig`). Thread-local storage gives each test's thread its own
/// independent value — no lock, no serialization, no race, since
/// `#[tokio::test]` (current-thread flavor, used throughout this crate)
/// pins a test's whole async call graph to the one thread that set it.
#[cfg(test)]
pub(crate) mod test_home {
use std::cell::RefCell;
use std::path::{Path, PathBuf};
thread_local! {
static HOME: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
}
/// Return the current thread's `$HOME` override, if one is set.
pub(crate) fn get() -> Option<PathBuf> {
HOME.with(|h| h.borrow().clone())
}
/// RAII guard: while held, this thread's [`super::transcript_path`]
/// calls resolve under the overridden home instead of the real
/// `$HOME`. Clears the override on drop.
pub(crate) struct HomeGuard;
impl Drop for HomeGuard {
fn drop(&mut self) {
HOME.with(|h| *h.borrow_mut() = None);
}
}
/// Override `$HOME` resolution for [`super::transcript_path`] calls
/// made on the current thread for the lifetime of the returned guard.
pub(crate) fn set(home: &Path) -> HomeGuard {
HOME.with(|h| *h.borrow_mut() = Some(home.to_path_buf()));
HomeGuard
}
}
/// Same as [`transcript_path`] but takes an explicit `$HOME` directory,
/// keeping the path-joining logic testable without mutating process env vars.
fn transcript_path_under_home(home: &Path, cwd: &Path, session_id: &str) -> PathBuf {
@@ -673,11 +673,7 @@ mod tests {
let project_root = tmp.path().join("project");
std::fs::create_dir_all(&project_root).unwrap();
let home = tempfile::tempdir().unwrap();
// SAFETY: this test owns HOME for its duration; no other test in this
// process reads HOME concurrently with this call.
unsafe {
std::env::set_var("HOME", home.path());
}
let _home_guard = crate::chat::compact::transcript::test_home::set(home.path());
let transcript_dir =
crate::chat::compact::transcript::transcript_path(&project_root, session_id)
.parent()
@@ -2108,11 +2108,7 @@ mod tests {
let project_root_dir = tempfile::tempdir().unwrap();
let project_root = project_root_dir.path().to_path_buf();
let home = tempfile::tempdir().unwrap();
// SAFETY: this test owns HOME for its duration; no other test in this
// process reads HOME concurrently with this call.
unsafe {
std::env::set_var("HOME", home.path());
}
let _home_guard = crate::chat::compact::transcript::test_home::set(home.path());
let transcript_dir =
crate::chat::compact::transcript::transcript_path(&project_root, session_id)
.parent()
+1 -5
View File
@@ -139,11 +139,7 @@ mod tests {
let session_id = "sess-compact-1";
let project_root = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
// SAFETY: this test owns HOME for its duration; no other test in this
// process reads HOME concurrently with this call.
unsafe {
std::env::set_var("HOME", home.path());
}
let _home_guard = crate::chat::compact::transcript::test_home::set(home.path());
let transcript_dir = compact::transcript::transcript_path(project_root.path(), session_id)
.parent()
.unwrap()