diff --git a/cargo-insta/tests/functional/main.rs b/cargo-insta/tests/functional/main.rs index 267f8996..b0e10626 100644 --- a/cargo-insta/tests/functional/main.rs +++ b/cargo-insta/tests/functional/main.rs @@ -865,3 +865,133 @@ fn test_snapshot() { "Expected line 5 to be numbered as 5, but got:\n{combined_output}" ); } + +#[test] +fn test_snapshot_with_merge_conflict() { + // A snapshot file containing git merge conflict markers should be detected + // and handled gracefully - the test continues and creates a new pending snapshot. + let test_project = TestFiles::new() + .add_cargo_toml("test_merge_conflict") + .add_file( + "src/lib.rs", + r#" +#[test] +fn test_snapshot() { + insta::assert_snapshot!("Hello, world!"); +} +"# + .to_string(), + ) + .add_file( + "src/snapshots/test_merge_conflict__snapshot.snap", + r#"<<<<<<< HEAD +--- +source: src/lib.rs +expression: "\"Hello, world!\"" +--- +Hello, world! +======= +--- +source: src/lib.rs +expression: "\"Hello, world!\"" +--- +Hello, world! (modified) +>>>>>>> feature-branch +"# + .to_string(), + ) + .create_project(); + + // Run the test + let output = test_project + .insta_cmd() + .args(["test"]) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .output() + .unwrap(); + + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + + // Inner test passes (merge conflict is detected and handled gracefully) + assert!( + stdout.contains("test test_snapshot ... ok"), + "Expected inner test to pass, got:\n{stdout}" + ); + + // Warning about merge conflicts is shown + assert!( + stderr.contains("unresolved merge conflicts"), + "Expected merge conflict warning, got:\n{stderr}" + ); + + // A new snapshot is stored + assert!( + stderr.contains("stored new snapshot"), + "Expected new snapshot to be stored, got:\n{stderr}" + ); +} + +#[test] +fn test_snapshot_with_merge_conflict_in_yaml() { + // Test case where the merge conflict occurs within the YAML metadata section. + // Conflict markers are detected before parsing, and the test continues gracefully. + let test_project = TestFiles::new() + .add_cargo_toml("test_merge_conflict_yaml") + .add_file( + "src/lib.rs", + r#" +#[test] +fn test_snapshot() { + insta::assert_snapshot!("Hello, world!"); +} +"# + .to_string(), + ) + .add_file( + "src/snapshots/test_merge_conflict_yaml__snapshot.snap", + r#"--- +source: src/lib.rs +<<<<<<< HEAD +expression: "\"Hello, world!\"" +======= +expression: "\"Hello, world! (modified)\"" +>>>>>>> feature-branch +--- +Hello, world! +"# + .to_string(), + ) + .create_project(); + + // Run the test + let output = test_project + .insta_cmd() + .args(["test"]) + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .output() + .unwrap(); + + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + + // Inner test passes (merge conflict is detected and handled gracefully) + assert!( + stdout.contains("test test_snapshot ... ok"), + "Expected inner test to pass, got:\n{stdout}" + ); + + // Warning about merge conflicts is shown + assert!( + stderr.contains("unresolved merge conflicts"), + "Expected merge conflict warning, got:\n{stderr}" + ); + + // A new snapshot is stored + assert!( + stderr.contains("stored new snapshot"), + "Expected new snapshot to be stored, got:\n{stderr}" + ); +} diff --git a/insta/src/runtime.rs b/insta/src/runtime.rs index 519869eb..409ce973 100644 --- a/insta/src/runtime.rs +++ b/insta/src/runtime.rs @@ -351,7 +351,29 @@ impl<'a> SnapshotAssertionContext<'a> { is_doctest, ); if fs::metadata(&file).is_ok() { - old_snapshot = Some(Snapshot::from_file(&file)?); + // Check for merge conflicts before parsing (line-based detection + // handles markers at any position and is more robust) + let has_conflict = fs::read_to_string(&file) + .map(|content| { + content.lines().any(|line| { + line.starts_with("<<<<<<<") + || line.starts_with("=======") + || line.starts_with(">>>>>>>") + }) + }) + .unwrap_or(false); + + if has_conflict { + elog!( + "{}: Snapshot file has unresolved merge conflicts, \ + ignoring: {}", + style("warning").yellow().bold(), + file.display() + ); + // old_snapshot stays None - treat as missing + } else { + old_snapshot = Some(Snapshot::from_file(&file)?); + } } snapshot_name = Some(name); snapshot_file = Some(file);