use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
pub struct TestRepo {
temp_dir: TempDir,
#[allow(dead_code)]
pub test_id: String,
created_worktrees: std::sync::Mutex<Vec<PathBuf>>,
}
impl TestRepo {
pub fn new() -> Self {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let test_id = uuid::Uuid::new_v4().to_string()[0..8].to_string();
Command::new("git")
.args(["init", "-b", "main"])
.current_dir(temp_dir.path())
.output()
.expect("Failed to init git repo");
Command::new("git")
.args(["config", "user.name", "Test User"])
.current_dir(temp_dir.path())
.output()
.expect("Failed to set git user name");
Command::new("git")
.args(["config", "user.email", "test@example.com"])
.current_dir(temp_dir.path())
.output()
.expect("Failed to set git user email");
std::fs::write(temp_dir.path().join("README.md"), "# Test Repo").unwrap();
Command::new("git")
.args(["add", "."])
.current_dir(temp_dir.path())
.output()
.expect("Failed to add files");
Command::new("git")
.args(["commit", "-m", "Initial commit"])
.current_dir(temp_dir.path())
.output()
.expect("Failed to commit");
Self {
temp_dir,
test_id,
created_worktrees: std::sync::Mutex::new(Vec::new()),
}
}
#[allow(dead_code)]
pub fn exec(&self, cmd: &[&str]) -> std::process::Output {
Command::new(cmd[0])
.args(&cmd[1..])
.current_dir(self.temp_dir.path())
.output()
.expect("Failed to execute command")
}
#[allow(dead_code)]
pub fn run_twin(&self, args: &[&str]) -> std::process::Output {
let twin_binary = Self::get_twin_binary();
Command::new(twin_binary)
.args(args)
.current_dir(self.temp_dir.path())
.output()
.expect("Failed to run twin")
}
#[allow(dead_code)]
pub fn worktree_path(&self, name: &str) -> String {
let path_str = format!("../test-{}-{}", name, self.test_id);
if let Ok(abs_path) = self
.temp_dir
.path()
.parent()
.unwrap()
.join(&path_str[3..])
.canonicalize()
{
self.created_worktrees.lock().unwrap().push(abs_path);
} else {
let expected_path = self.temp_dir.path().parent().unwrap().join(&path_str[3..]);
self.created_worktrees.lock().unwrap().push(expected_path);
}
path_str
}
pub fn path(&self) -> &Path {
self.temp_dir.path()
}
#[allow(dead_code)]
fn get_twin_binary() -> PathBuf {
let exe_path = std::env::current_exe().unwrap();
let target_dir = exe_path.parent().unwrap().parent().unwrap();
target_dir.join("twin")
}
}
impl Drop for TestRepo {
fn drop(&mut self) {
let worktrees = self.created_worktrees.lock().unwrap();
for worktree_path in worktrees.iter() {
if worktree_path.exists() {
Command::new("git")
.args([
"worktree",
"remove",
"--force",
&worktree_path.to_string_lossy(),
])
.current_dir(self.temp_dir.path())
.output()
.ok();
if worktree_path.exists() {
std::fs::remove_dir_all(worktree_path).ok();
}
}
}
}
}