use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
pub struct TestContext {
#[allow(dead_code)]
dir: TempDir,
pub path: PathBuf,
}
impl TestContext {
pub fn new() -> Self {
let dir = TempDir::new().expect("Failed to create temp dir");
let path = dir.path().to_path_buf();
run_git(&path, &["init"]);
run_git(&path, &["config", "user.email", "test@example.com"]);
run_git(&path, &["config", "user.name", "Test User"]);
std::fs::write(path.join("README.md"), "# Test Repository\n")
.expect("Failed to write README");
run_git(&path, &["add", "."]);
run_git(&path, &["commit", "-m", "Initial commit"]);
Self { dir, path }
}
pub fn with_stkd() -> Self {
let ctx = Self::new();
run_stkd(&ctx.path, &["init"]);
ctx
}
pub fn current_branch(&self) -> String {
let output = Command::new("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.current_dir(&self.path)
.output()
.expect("Failed to get current branch");
String::from_utf8(output.stdout)
.expect("Invalid UTF-8")
.trim()
.to_string()
}
pub fn commit_file(&self, filename: &str, content: &str, message: &str) {
std::fs::write(self.path.join(filename), content)
.expect("Failed to write file");
run_git(&self.path, &["add", filename]);
run_git(&self.path, &["commit", "-m", message]);
}
pub fn branches(&self) -> Vec<String> {
let output = Command::new("git")
.args(["branch", "--format=%(refname:short)"])
.current_dir(&self.path)
.output()
.expect("Failed to list branches");
String::from_utf8(output.stdout)
.expect("Invalid UTF-8")
.lines()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
}
pub fn branch_exists(&self, name: &str) -> bool {
self.branches().contains(&name.to_string())
}
pub fn commit_count(&self) -> usize {
let output = Command::new("git")
.args(["rev-list", "--count", "HEAD"])
.current_dir(&self.path)
.output()
.expect("Failed to count commits");
String::from_utf8(output.stdout)
.expect("Invalid UTF-8")
.trim()
.parse()
.unwrap_or(0)
}
pub fn head_sha(&self) -> String {
let output = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&self.path)
.output()
.expect("Failed to get HEAD SHA");
String::from_utf8(output.stdout)
.expect("Invalid UTF-8")
.trim()
.to_string()
}
pub fn is_clean(&self) -> bool {
let output = Command::new("git")
.args(["status", "--porcelain"])
.current_dir(&self.path)
.output()
.expect("Failed to get git status");
output.stdout.is_empty()
}
}
impl Default for TestContext {
fn default() -> Self {
Self::new()
}
}
pub fn run_git(dir: &Path, args: &[&str]) -> std::process::Output {
let output = Command::new("git")
.args(args)
.current_dir(dir)
.output()
.unwrap_or_else(|e| panic!("Failed to run git {:?}: {}", args, e));
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
panic!("git {:?} failed: {}", args, stderr);
}
output
}
pub fn run_stkd(dir: &Path, args: &[&str]) -> std::process::Output {
let stkd_bin = find_stkd_binary();
Command::new(&stkd_bin)
.args(args)
.current_dir(dir)
.output()
.unwrap_or_else(|e| panic!("Failed to run stkd {:?}: {} (binary: {})", args, e, stkd_bin))
}
fn find_stkd_binary() -> String {
if let Ok(bin) = std::env::var("CARGO_BIN_EXE_gt") {
return bin;
}
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let workspace_root = std::path::Path::new(manifest_dir)
.parent() .and_then(|p| p.parent()) .expect("Could not find workspace root");
let debug_bin = workspace_root.join("target/debug/gt");
if debug_bin.exists() {
return debug_bin.to_string_lossy().to_string();
}
let release_bin = workspace_root.join("target/release/gt");
if release_bin.exists() {
return release_bin.to_string_lossy().to_string();
}
"gt".to_string()
}
pub fn run_stkd_success(dir: &Path, args: &[&str]) -> std::process::Output {
let output = run_stkd(dir, args);
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
panic!(
"stkd {:?} failed:\nstdout: {}\nstderr: {}",
args, stdout, stderr
);
}
output
}
#[allow(dead_code)]
pub fn run_stkd_failure(dir: &Path, args: &[&str]) -> std::process::Output {
let output = run_stkd(dir, args);
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
panic!("stkd {:?} should have failed but succeeded:\n{}", args, stdout);
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_creates_valid_repo() {
let ctx = TestContext::new();
assert!(ctx.path.exists());
assert!(ctx.path.join(".git").exists());
assert_eq!(ctx.current_branch(), "master");
assert!(ctx.is_clean());
}
#[test]
fn test_context_commit_file() {
let ctx = TestContext::new();
let initial_count = ctx.commit_count();
ctx.commit_file("test.txt", "Hello", "Add test file");
assert_eq!(ctx.commit_count(), initial_count + 1);
assert!(ctx.path.join("test.txt").exists());
}
}