Skip to content

Commit 5b82d7a

Browse files
simegclaude
andcommitted
Refactor test files to eliminate code duplication
- Remove duplicate create_test_repo() functions from test_fixup.rs, test_upstream.rs, and test_sync.rs - Extend tests/common/mod.rs with enhanced test utilities: - TestUtils for directory management and command helpers - TestRepoBuilder for flexible test repository creation - TestAssertions for common test patterns - Enhanced TestRepo with create_commit_with_hash(), stage_files(), etc. - Update test files to use shared utilities instead of duplicated code - Eliminate ~120+ lines of duplicated test code across multiple files - Maintain all existing test functionality while improving maintainability 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 1e139a5 commit 5b82d7a

File tree

4 files changed

+325
-314
lines changed

4 files changed

+325
-314
lines changed

tests/common/mod.rs

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,3 +436,197 @@ pub fn repo_with_remote_ahead(branch_name: &str) -> (TestRepo, TestRepo) {
436436

437437
(repo, remote)
438438
}
439+
440+
/// Test utilities for common testing patterns
441+
pub struct TestUtils;
442+
443+
impl TestUtils {
444+
/// Run a test function in a specific directory, automatically restoring the original directory
445+
pub fn with_current_dir<F, R>(dir: &Path, test_fn: F) -> R
446+
where
447+
F: FnOnce() -> R,
448+
{
449+
let original_dir = std::env::current_dir().expect("Failed to get current directory");
450+
std::env::set_current_dir(dir).expect("Failed to change directory");
451+
452+
let result = test_fn();
453+
454+
// Always restore directory, even if the test panics
455+
let _ = std::env::set_current_dir(&original_dir);
456+
result
457+
}
458+
459+
/// Run a git-x command and return the Command for assertions
460+
pub fn run_git_x_cmd(_args: &[&str]) -> Command {
461+
Command::cargo_bin("git-x").expect("Failed to find git-x binary")
462+
}
463+
464+
/// Create a git-x command with args for testing
465+
pub fn git_x_with_args(args: &[&str]) -> Command {
466+
let mut cmd = Self::run_git_x_cmd(args);
467+
cmd.args(args);
468+
cmd
469+
}
470+
471+
/// Test that a command runs without panicking in a repo directory
472+
pub fn test_command_in_repo<F>(repo: &TestRepo, test_fn: F)
473+
where
474+
F: FnOnce(),
475+
{
476+
Self::with_current_dir(repo.path(), test_fn)
477+
}
478+
479+
/// Test that a command handles non-git directories gracefully
480+
pub fn test_command_outside_repo<F>(test_fn: F)
481+
where
482+
F: FnOnce(),
483+
{
484+
let temp_dir = tempdir().expect("Failed to create temp directory");
485+
Self::with_current_dir(temp_dir.path(), test_fn)
486+
}
487+
}
488+
489+
/// Enhanced test repository creation with more options
490+
pub struct TestRepoBuilder {
491+
branch_name: Option<String>,
492+
commit_count: usize,
493+
with_remote: bool,
494+
conventional_commits: bool,
495+
}
496+
497+
impl TestRepoBuilder {
498+
pub fn new() -> Self {
499+
Self {
500+
branch_name: None,
501+
commit_count: 1,
502+
with_remote: false,
503+
conventional_commits: false,
504+
}
505+
}
506+
507+
pub fn with_branch(mut self, branch_name: &str) -> Self {
508+
self.branch_name = Some(branch_name.to_string());
509+
self
510+
}
511+
512+
pub fn with_commits(mut self, count: usize) -> Self {
513+
self.commit_count = count;
514+
self
515+
}
516+
517+
pub fn with_remote(mut self) -> Self {
518+
self.with_remote = true;
519+
self
520+
}
521+
522+
pub fn with_conventional_commits(mut self) -> Self {
523+
self.conventional_commits = true;
524+
self
525+
}
526+
527+
pub fn build(self) -> TestRepo {
528+
if self.conventional_commits {
529+
return repo_with_conventional_commits();
530+
}
531+
532+
let repo = if let Some(branch_name) = &self.branch_name {
533+
repo_with_branch(branch_name)
534+
} else {
535+
basic_repo()
536+
};
537+
538+
// Add additional commits if requested
539+
for i in 1..self.commit_count {
540+
repo.add_commit(
541+
&format!("file{}.txt", i + 1),
542+
&format!("content {}", i + 1),
543+
&format!("commit {}", i + 1),
544+
);
545+
}
546+
547+
if self.with_remote {
548+
let branch = self.branch_name.as_deref().unwrap_or("main");
549+
repo.setup_remote(branch);
550+
}
551+
552+
repo
553+
}
554+
}
555+
556+
impl Default for TestRepoBuilder {
557+
fn default() -> Self {
558+
Self::new()
559+
}
560+
}
561+
562+
/// Assertion helpers for common test patterns
563+
pub struct TestAssertions;
564+
565+
impl TestAssertions {
566+
/// Assert that output contains expected success indicators
567+
pub fn assert_success_output(output: &str) {
568+
assert!(
569+
output.contains("✅") || output.contains("success"),
570+
"Expected success indicators in output: {output}"
571+
);
572+
}
573+
574+
/// Assert that output contains expected error indicators
575+
pub fn assert_error_output(output: &str) {
576+
assert!(
577+
output.contains("❌") || output.contains("error") || output.contains("failed"),
578+
"Expected error indicators in output: {output}"
579+
);
580+
}
581+
582+
/// Assert that a git command was successful in a repo
583+
pub fn assert_git_command_success(repo: &TestRepo, args: &[&str]) {
584+
StdCommand::new("git")
585+
.args(args)
586+
.current_dir(repo.path())
587+
.assert()
588+
.success();
589+
}
590+
591+
/// Get git command output as string
592+
pub fn get_git_output(repo: &TestRepo, args: &[&str]) -> String {
593+
let output = StdCommand::new("git")
594+
.args(args)
595+
.current_dir(repo.path())
596+
.output()
597+
.expect("Failed to run git command");
598+
599+
String::from_utf8_lossy(&output.stdout).trim().to_string()
600+
}
601+
}
602+
603+
/// Additional repository helper functions
604+
impl TestRepo {
605+
/// Create a commit and return its hash
606+
pub fn create_commit_with_hash(&self, filename: &str, content: &str, message: &str) -> String {
607+
self.add_commit(filename, content, message);
608+
TestAssertions::get_git_output(self, &["rev-parse", "HEAD"])
609+
}
610+
611+
/// Stage specific files (as opposed to add_commit which stages all files)
612+
pub fn stage_files(&self, files: &[&str]) {
613+
for file in files {
614+
StdCommand::new("git")
615+
.args(["add", file])
616+
.current_dir(&self.path)
617+
.assert()
618+
.success();
619+
}
620+
}
621+
622+
/// Get current commit hash
623+
pub fn get_current_commit_hash(&self) -> String {
624+
TestAssertions::get_git_output(self, &["rev-parse", "HEAD"])
625+
}
626+
627+
/// Check if there are staged changes
628+
pub fn has_staged_changes(&self) -> bool {
629+
let output = TestAssertions::get_git_output(self, &["diff", "--cached", "--name-only"]);
630+
!output.is_empty()
631+
}
632+
}

tests/test_fixup.rs

Lines changed: 18 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,12 @@
1+
mod common;
2+
13
use assert_cmd::Command;
4+
use common::basic_repo;
25
use git_x::fixup::*;
36
use predicates::prelude::*;
47
use std::fs;
5-
use std::path::PathBuf;
68
use tempfile::TempDir;
79

8-
// Helper function to create a test git repository
9-
fn create_test_repo() -> (TempDir, PathBuf) {
10-
let temp_dir = TempDir::new().expect("Failed to create temp directory");
11-
let repo_path = temp_dir.path().to_path_buf();
12-
13-
// Initialize git repo
14-
Command::new("git")
15-
.args(["init"])
16-
.current_dir(&repo_path)
17-
.assert()
18-
.success();
19-
20-
// Configure git
21-
Command::new("git")
22-
.args(["config", "user.name", "Test User"])
23-
.current_dir(&repo_path)
24-
.assert()
25-
.success();
26-
27-
Command::new("git")
28-
.args(["config", "user.email", "test@example.com"])
29-
.current_dir(&repo_path)
30-
.assert()
31-
.success();
32-
33-
// Create initial commit
34-
fs::write(repo_path.join("README.md"), "Initial commit").expect("Failed to write file");
35-
Command::new("git")
36-
.args(["add", "README.md"])
37-
.current_dir(&repo_path)
38-
.assert()
39-
.success();
40-
41-
Command::new("git")
42-
.args(["commit", "-m", "Initial commit"])
43-
.current_dir(&repo_path)
44-
.assert()
45-
.success();
46-
47-
(temp_dir, repo_path)
48-
}
49-
50-
// Helper function to create a commit and return its hash
51-
fn create_commit(repo_path: &PathBuf, filename: &str, content: &str, message: &str) -> String {
52-
fs::write(repo_path.join(filename), content).expect("Failed to write file");
53-
Command::new("git")
54-
.args(["add", filename])
55-
.current_dir(repo_path)
56-
.assert()
57-
.success();
58-
59-
Command::new("git")
60-
.args(["commit", "-m", message])
61-
.current_dir(repo_path)
62-
.assert()
63-
.success();
64-
65-
// Get the commit hash
66-
let output = Command::new("git")
67-
.args(["rev-parse", "HEAD"])
68-
.current_dir(repo_path)
69-
.output()
70-
.expect("Failed to get commit hash");
71-
72-
String::from_utf8_lossy(&output.stdout).trim().to_string()
73-
}
74-
7510
#[test]
7611
fn test_is_valid_commit_hash_format() {
7712
// Valid hashes
@@ -193,68 +128,51 @@ fn test_fixup_run_function_outside_git_repo() {
193128

194129
#[test]
195130
fn test_fixup_invalid_commit_hash() {
196-
let (_temp_dir, repo_path) = create_test_repo();
131+
let repo = basic_repo();
197132

198-
let mut cmd = Command::cargo_bin("git-x").expect("Failed to find binary");
199-
cmd.args(["fixup", "nonexistent123"])
200-
.current_dir(&repo_path)
201-
.assert()
133+
repo.run_git_x(&["fixup", "nonexistent123"])
202134
.success() // The command succeeds but shows an error message
203135
.stderr(predicate::str::contains("Commit hash does not exist"));
204136
}
205137

206138
#[test]
207139
fn test_fixup_no_staged_changes() {
208-
let (_temp_dir, repo_path) = create_test_repo();
209-
let commit_hash = create_commit(&repo_path, "test.txt", "test content", "Test commit");
140+
let repo = basic_repo();
141+
let commit_hash = repo.create_commit_with_hash("test.txt", "test content", "Test commit");
210142

211-
let mut cmd = Command::cargo_bin("git-x").expect("Failed to find binary");
212-
cmd.args(["fixup", &commit_hash[0..7]]) // Use short hash
213-
.current_dir(&repo_path)
214-
.assert()
143+
repo.run_git_x(&["fixup", &commit_hash[0..7]]) // Use short hash
215144
.success() // The command succeeds but shows an error message
216145
.stderr(predicate::str::contains("No staged changes found"));
217146
}
218147

219148
#[test]
220149
fn test_fixup_with_staged_changes() {
221-
let (_temp_dir, repo_path) = create_test_repo();
222-
let commit_hash = create_commit(&repo_path, "test.txt", "test content", "Test commit");
150+
let repo = basic_repo();
151+
let commit_hash = repo.create_commit_with_hash("test.txt", "test content", "Test commit");
223152

224153
// Create and stage some changes
225-
fs::write(repo_path.join("test.txt"), "modified content").expect("Failed to write file");
226-
Command::new("git")
227-
.args(["add", "test.txt"])
228-
.current_dir(&repo_path)
229-
.assert()
230-
.success();
154+
fs::write(repo.path().join("test.txt"), "modified content").expect("Failed to write file");
155+
repo.stage_files(&["test.txt"]);
231156

232-
let mut cmd = Command::cargo_bin("git-x").expect("Failed to find binary");
233-
cmd.args(["fixup", &commit_hash[0..7]]) // Use short hash
234-
.current_dir(&repo_path)
235-
.assert()
157+
repo.run_git_x(&["fixup", &commit_hash[0..7]]) // Use short hash
236158
.success()
237159
.stdout(predicate::str::contains("Fixup commit created"))
238160
.stdout(predicate::str::contains("To squash the fixup commit"));
239161
}
240162

241163
#[test]
242164
fn test_fixup_with_rebase_flag() {
243-
let (_temp_dir, repo_path) = create_test_repo();
244-
let commit_hash = create_commit(&repo_path, "test.txt", "test content", "Test commit");
165+
let repo = basic_repo();
166+
let commit_hash = repo.create_commit_with_hash("test.txt", "test content", "Test commit");
245167

246168
// Create and stage some changes
247-
fs::write(repo_path.join("test.txt"), "modified content").expect("Failed to write file");
248-
Command::new("git")
249-
.args(["add", "test.txt"])
250-
.current_dir(&repo_path)
251-
.assert()
252-
.success();
169+
fs::write(repo.path().join("test.txt"), "modified content").expect("Failed to write file");
170+
repo.stage_files(&["test.txt"]);
253171

254172
// Set environment to make interactive rebase work in tests
255173
let mut cmd = Command::cargo_bin("git-x").expect("Failed to find binary");
256174
cmd.args(["fixup", &commit_hash[0..7], "--rebase"])
257-
.current_dir(&repo_path)
175+
.current_dir(repo.path())
258176
.env("GIT_SEQUENCE_EDITOR", "true") // Auto-accept rebase plan
259177
.assert()
260178
.success()

0 commit comments

Comments
 (0)