Skip to content

Commit de8648d

Browse files
committed
add failure test cases
1 parent b84fac6 commit de8648d

File tree

2 files changed

+738
-0
lines changed

2 files changed

+738
-0
lines changed

tests/fork_point_failure.rs

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#[path = "common/mod.rs"]
2+
pub mod common;
3+
4+
use common::{
5+
create_new_file, first_commit_all, generate_path_to_repo, run_git_command, run_test_bin,
6+
run_test_bin_expect_ok, setup_git_repo, teardown_git_repo,
7+
};
8+
9+
use git2::RepositoryState;
10+
use std::path::Path;
11+
12+
/// Helper function to run git-chain and check for error messages in the output
13+
/// This is useful when we want to verify error messages in stderr without expecting
14+
/// a non-zero exit code (since git-chain may handle some errors gracefully)
15+
fn run_and_check_for_error_messages<P: AsRef<Path>>(
16+
current_dir: P,
17+
args: Vec<&str>,
18+
) -> (String, String) {
19+
let output = run_test_bin(current_dir, args);
20+
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
21+
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
22+
23+
println!("STDOUT: {}", stdout);
24+
println!("STDERR: {}", stderr);
25+
26+
(stdout, stderr)
27+
}
28+
29+
/// This test creates a scenario with completely unrelated Git branches.
30+
///
31+
/// This simulates what can happen in real-world scenarios when:
32+
/// 1. Branch histories get completely rewritten (force pushed)
33+
/// 2. Git's reflog entries expire
34+
/// 3. Two branches that are supposed to be related end up having no common ancestor
35+
#[test]
36+
fn test_natural_forkpoint_loss() {
37+
let repo_name = "natural_forkpoint_loss_test";
38+
let repo = setup_git_repo(repo_name);
39+
let path_to_repo = generate_path_to_repo(repo_name);
40+
41+
// Create initial commit on master
42+
create_new_file(&path_to_repo, "init.txt", "Initial content");
43+
first_commit_all(&repo, "Initial commit");
44+
45+
// Create an intentionally broken branch chain where branch1 and branch2 have no common ancestor
46+
47+
// Branch1 - create normally from master
48+
run_git_command(&path_to_repo, vec!["checkout", "-b", "branch1"]);
49+
create_new_file(&path_to_repo, "branch1_file.txt", "Branch 1 content");
50+
run_git_command(&path_to_repo, vec!["add", "branch1_file.txt"]);
51+
run_git_command(&path_to_repo, vec!["commit", "-m", "Branch 1 commit"]);
52+
53+
// Branch2 - create as an orphan branch (with no relationship to master or branch1)
54+
run_git_command(&path_to_repo, vec!["checkout", "--orphan", "branch2"]);
55+
run_git_command(&path_to_repo, vec!["rm", "-rf", "."]); // Clear the working directory
56+
create_new_file(&path_to_repo, "branch2_file.txt", "Branch 2 content");
57+
run_git_command(&path_to_repo, vec!["add", "branch2_file.txt"]);
58+
run_git_command(&path_to_repo, vec!["commit", "-m", "Branch 2 commit"]);
59+
60+
// Branch3 - create from branch2
61+
run_git_command(&path_to_repo, vec!["checkout", "-b", "branch3"]);
62+
create_new_file(&path_to_repo, "branch3_file.txt", "Branch 3 content");
63+
run_git_command(&path_to_repo, vec!["add", "branch3_file.txt"]);
64+
run_git_command(&path_to_repo, vec!["commit", "-m", "Branch 3 commit"]);
65+
66+
// Set up a chain with these branches
67+
// Note: git-chain's setup command doesn't verify branch relationships at setup time
68+
run_test_bin_expect_ok(
69+
&path_to_repo,
70+
vec![
71+
"setup",
72+
"test_chain",
73+
"master",
74+
"branch1",
75+
"branch2",
76+
"branch3",
77+
],
78+
);
79+
80+
// Print the current branch structure
81+
println!("Branch Setup:");
82+
let chain_branches = run_git_command(&path_to_repo, vec!["branch", "-v"]);
83+
println!("{}", String::from_utf8_lossy(&chain_branches.stdout));
84+
85+
// Confirm that branch1 and branch2 have no merge base
86+
println!("\nVerifying no merge base between branch1 and branch2:");
87+
let merge_base_cmd = run_git_command(&path_to_repo, vec!["merge-base", "branch1", "branch2"]);
88+
println!(
89+
"stdout: {}",
90+
String::from_utf8_lossy(&merge_base_cmd.stdout)
91+
);
92+
println!(
93+
"stderr: {}",
94+
String::from_utf8_lossy(&merge_base_cmd.stderr)
95+
);
96+
97+
// Also check that fork-point detection fails
98+
println!("\nFork-point detection between branch1 and branch2:");
99+
let fork_point_cmd = run_git_command(
100+
&path_to_repo,
101+
vec!["merge-base", "--fork-point", "branch1", "branch2"],
102+
);
103+
println!(
104+
"stdout: {}",
105+
String::from_utf8_lossy(&fork_point_cmd.stdout)
106+
);
107+
println!(
108+
"stderr: {}",
109+
String::from_utf8_lossy(&fork_point_cmd.stderr)
110+
);
111+
112+
// Now try to rebase the chain - this should produce errors during fork-point detection
113+
println!("\nRunning git-chain rebase:");
114+
let (stdout, stderr) = run_and_check_for_error_messages(&path_to_repo, vec!["rebase"]);
115+
116+
// Check for error messages about missing fork points or merge bases
117+
let error_patterns = [
118+
"no merge base found",
119+
"Unable to get forkpoint",
120+
"common ancestor",
121+
"failed to find",
122+
];
123+
124+
let has_error_message = error_patterns
125+
.iter()
126+
.any(|pattern| stderr.contains(pattern) || stdout.contains(pattern));
127+
128+
assert!(has_error_message,
129+
"Expected output to contain error about missing merge base or fork point.\nStdout: {}\nStderr: {}",
130+
stdout, stderr);
131+
132+
// Clean up any rebase in progress
133+
if repo.state() != RepositoryState::Clean {
134+
run_git_command(&path_to_repo, vec!["rebase", "--abort"]);
135+
}
136+
137+
// Clean up test repository
138+
teardown_git_repo(repo_name);
139+
}
140+
141+
/// This test creates a chain with completely unrelated branches to test edge cases.
142+
#[test]
143+
fn test_unable_to_get_forkpoint_error() {
144+
let repo_name = "forkpoint_error_test";
145+
let repo = setup_git_repo(repo_name);
146+
let path_to_repo = generate_path_to_repo(repo_name);
147+
148+
// Create initial commit on master
149+
create_new_file(&path_to_repo, "init.txt", "Initial content");
150+
first_commit_all(&repo, "Initial commit");
151+
152+
// Create completely unrelated branches
153+
// Branch1 - create orphan branch with its own history
154+
run_git_command(&path_to_repo, vec!["checkout", "--orphan", "branch1"]);
155+
run_git_command(&path_to_repo, vec!["rm", "-rf", "."]);
156+
create_new_file(&path_to_repo, "branch1.txt", "Branch 1 content");
157+
run_git_command(&path_to_repo, vec!["add", "branch1.txt"]);
158+
run_git_command(&path_to_repo, vec!["commit", "-m", "Branch 1 commit"]);
159+
160+
// Branch2 - another orphan branch with different history
161+
run_git_command(&path_to_repo, vec!["checkout", "--orphan", "branch2"]);
162+
run_git_command(&path_to_repo, vec!["rm", "-rf", "."]);
163+
create_new_file(&path_to_repo, "branch2.txt", "Branch 2 content");
164+
run_git_command(&path_to_repo, vec!["add", "branch2.txt"]);
165+
run_git_command(&path_to_repo, vec!["commit", "-m", "Branch 2 commit"]);
166+
167+
// Set up a chain with these completely unrelated branches
168+
let args: Vec<&str> = vec!["setup", "unrelated_chain", "master", "branch1", "branch2"];
169+
170+
// Setup should succeed - git-chain doesn't verify branch relationships at setup time
171+
run_test_bin_expect_ok(&path_to_repo, args);
172+
173+
// Run rebase with our unrelated branches
174+
println!("Running git-chain rebase with unrelated branches:");
175+
let (stdout, stderr) = run_and_check_for_error_messages(&path_to_repo, vec!["rebase"]);
176+
177+
// Check for error messages about missing fork points or merge bases
178+
let error_patterns = [
179+
"no merge base found",
180+
"Unable to get forkpoint",
181+
"common ancestor",
182+
"failed to find",
183+
];
184+
185+
let has_error_message = error_patterns
186+
.iter()
187+
.any(|pattern| stderr.contains(pattern) || stdout.contains(pattern));
188+
189+
assert!(has_error_message,
190+
"Expected output to contain error about missing merge base or fork point.\nStdout: {}\nStderr: {}",
191+
stdout, stderr);
192+
193+
// Clean up test repo
194+
teardown_git_repo(repo_name);
195+
}
196+
197+
/// Tests for a rebase conflict scenario.
198+
///
199+
/// This test creates a situation where branches have conflicts that
200+
/// will cause the rebase to fail. This tests git-chain's ability to detect and
201+
/// report conflicts during the rebase process.
202+
#[test]
203+
fn test_rebase_conflict_error() {
204+
let repo_name = "rebase_conflict_error";
205+
let repo = setup_git_repo(repo_name);
206+
let path_to_repo = generate_path_to_repo(repo_name);
207+
208+
// Create initial commit on master
209+
create_new_file(&path_to_repo, "init.txt", "Initial content");
210+
first_commit_all(&repo, "Initial commit");
211+
212+
// Create branch1 from master
213+
run_git_command(&path_to_repo, vec!["branch", "branch1"]);
214+
run_git_command(&path_to_repo, vec!["checkout", "branch1"]);
215+
create_new_file(&path_to_repo, "branch1.txt", "Branch 1 content");
216+
run_git_command(&path_to_repo, vec!["add", "branch1.txt"]);
217+
run_git_command(&path_to_repo, vec!["commit", "-m", "Branch 1 commit"]);
218+
219+
// Create branch2 from branch1
220+
run_git_command(&path_to_repo, vec!["branch", "branch2"]);
221+
run_git_command(&path_to_repo, vec!["checkout", "branch2"]);
222+
create_new_file(&path_to_repo, "branch2.txt", "Branch 2 content");
223+
run_git_command(&path_to_repo, vec!["add", "branch2.txt"]);
224+
run_git_command(&path_to_repo, vec!["commit", "-m", "Branch 2 commit"]);
225+
226+
// Set up a chain
227+
run_test_bin_expect_ok(
228+
&path_to_repo,
229+
vec!["setup", "test_chain", "master", "branch1", "branch2"],
230+
);
231+
232+
// Create a scenario where rebasing would create a conflict:
233+
// Both master and branch1 modify the same file in different ways
234+
run_git_command(&path_to_repo, vec!["checkout", "master"]);
235+
create_new_file(&path_to_repo, "conflict.txt", "Master content");
236+
run_git_command(&path_to_repo, vec!["add", "conflict.txt"]);
237+
run_git_command(&path_to_repo, vec!["commit", "-m", "Add file on master"]);
238+
239+
run_git_command(&path_to_repo, vec!["checkout", "branch1"]);
240+
create_new_file(&path_to_repo, "conflict.txt", "Branch1 content");
241+
run_git_command(&path_to_repo, vec!["add", "conflict.txt"]);
242+
run_git_command(
243+
&path_to_repo,
244+
vec!["commit", "-m", "Add conflicting file on branch1"],
245+
);
246+
247+
// Try rebasing - this should fail due to conflict
248+
println!("Running git-chain rebase with conflicting changes:");
249+
let (stdout, stderr) = run_and_check_for_error_messages(&path_to_repo, vec!["rebase"]);
250+
251+
// We expect to see a message about resolving rebase conflicts
252+
let has_conflict_message = stderr.contains("conflict")
253+
|| stderr.contains("error")
254+
|| stderr.contains("Unable to")
255+
|| stdout.contains("conflict")
256+
|| stdout.contains("CONFLICT");
257+
258+
assert!(
259+
has_conflict_message,
260+
"Expected message about rebase conflict.\nStdout: {}\nStderr: {}",
261+
stdout, stderr
262+
);
263+
264+
// Clean up any rebase in progress
265+
if repo.state() != RepositoryState::Clean {
266+
run_git_command(&path_to_repo, vec!["rebase", "--abort"]);
267+
}
268+
269+
teardown_git_repo(repo_name);
270+
}

0 commit comments

Comments
 (0)