| use super::*; |
| use crate::Hunk; |
| use crate::apply_patch; |
| use crate::parse_patch; |
| use codex_exec_server::LOCAL_FS; |
| use codex_utils_path_uri::PathUri; |
| use pretty_assertions::assert_eq; |
| use std::fs; |
| use tempfile::tempdir; |
|
|
| fn wrap_patch(body: &str) -> String { |
| format!("*** Begin Patch\n{body}\n*** End Patch") |
| } |
|
|
| #[tokio::test] |
| async fn test_unified_diff() { |
| |
| let dir = tempdir().unwrap(); |
| let path = dir.path().join("multi.txt"); |
| fs::write(&path, "foo\nbar\nbaz\nqux\n").unwrap(); |
| let patch = wrap_patch(&format!( |
| r#"*** Update File: {} |
| @@ |
| foo |
| -bar |
| +BAR |
| @@ |
| baz |
| -qux |
| +QUX"#, |
| path.display() |
| )); |
| let patch = parse_patch(&patch).unwrap(); |
|
|
| let update_file_chunks = match patch.hunks.as_slice() { |
| [Hunk::UpdateFile { chunks, .. }] => chunks, |
| _ => panic!("Expected a single UpdateFile hunk"), |
| }; |
| let path_uri = PathUri::from_host_native_path(&path).expect("absolute test path"); |
| let diff = unified_diff_from_chunks( |
| &path_uri, |
| update_file_chunks, |
| LOCAL_FS.as_ref(), |
| None, |
| ) |
| .await |
| .unwrap(); |
| let expected_diff = r#"@@ -1,4 +1,4 @@ |
| foo |
| -bar |
| +BAR |
| baz |
| -qux |
| +QUX |
| "#; |
| let expected = ApplyPatchFileUpdate { |
| unified_diff: expected_diff.to_string(), |
| original_content: "foo\nbar\nbaz\nqux\n".to_string(), |
| content: "foo\nBAR\nbaz\nQUX\n".to_string(), |
| }; |
| assert_eq!(expected, diff); |
| } |
|
|
| #[tokio::test] |
| async fn test_unified_diff_first_line_replacement() { |
| |
| let dir = tempdir().unwrap(); |
| let path = dir.path().join("first.txt"); |
| fs::write(&path, "foo\nbar\nbaz\n").unwrap(); |
|
|
| let patch = wrap_patch(&format!( |
| r#"*** Update File: {} |
| @@ |
| -foo |
| +FOO |
| bar |
| "#, |
| path.display() |
| )); |
|
|
| let patch = parse_patch(&patch).unwrap(); |
| let chunks = match patch.hunks.as_slice() { |
| [Hunk::UpdateFile { chunks, .. }] => chunks, |
| _ => panic!("Expected a single UpdateFile hunk"), |
| }; |
|
|
| let resolved_path = PathUri::from_host_native_path(&path).expect("absolute test path"); |
| let diff = unified_diff_from_chunks( |
| &resolved_path, |
| chunks, |
| LOCAL_FS.as_ref(), |
| None, |
| ) |
| .await |
| .unwrap(); |
| let expected_diff = r#"@@ -1,2 +1,2 @@ |
| -foo |
| +FOO |
| bar |
| "#; |
| let expected = ApplyPatchFileUpdate { |
| unified_diff: expected_diff.to_string(), |
| original_content: "foo\nbar\nbaz\n".to_string(), |
| content: "FOO\nbar\nbaz\n".to_string(), |
| }; |
| assert_eq!(expected, diff); |
| } |
|
|
| #[tokio::test] |
| async fn test_unified_diff_last_line_replacement() { |
| |
| let dir = tempdir().unwrap(); |
| let path = dir.path().join("last.txt"); |
| fs::write(&path, "foo\nbar\nbaz\n").unwrap(); |
|
|
| let patch = wrap_patch(&format!( |
| r#"*** Update File: {} |
| @@ |
| foo |
| bar |
| -baz |
| +BAZ |
| "#, |
| path.display() |
| )); |
|
|
| let patch = parse_patch(&patch).unwrap(); |
| let chunks = match patch.hunks.as_slice() { |
| [Hunk::UpdateFile { chunks, .. }] => chunks, |
| _ => panic!("Expected a single UpdateFile hunk"), |
| }; |
|
|
| let resolved_path = PathUri::from_host_native_path(&path).expect("absolute test path"); |
| let diff = unified_diff_from_chunks( |
| &resolved_path, |
| chunks, |
| LOCAL_FS.as_ref(), |
| None, |
| ) |
| .await |
| .unwrap(); |
| let expected_diff = r#"@@ -2,2 +2,2 @@ |
| bar |
| -baz |
| +BAZ |
| "#; |
| let expected = ApplyPatchFileUpdate { |
| unified_diff: expected_diff.to_string(), |
| original_content: "foo\nbar\nbaz\n".to_string(), |
| content: "foo\nbar\nBAZ\n".to_string(), |
| }; |
| assert_eq!(expected, diff); |
| } |
|
|
| #[tokio::test] |
| async fn test_unified_diff_insert_at_eof() { |
| |
| let dir = tempdir().unwrap(); |
| let path = dir.path().join("insert.txt"); |
| fs::write(&path, "foo\nbar\nbaz\n").unwrap(); |
|
|
| let patch = wrap_patch(&format!( |
| r#"*** Update File: {} |
| @@ |
| +quux |
| *** End of File |
| "#, |
| path.display() |
| )); |
|
|
| let patch = parse_patch(&patch).unwrap(); |
| let chunks = match patch.hunks.as_slice() { |
| [Hunk::UpdateFile { chunks, .. }] => chunks, |
| _ => panic!("Expected a single UpdateFile hunk"), |
| }; |
|
|
| let path_uri = PathUri::from_host_native_path(&path).expect("absolute test path"); |
| let diff = |
| unified_diff_from_chunks(&path_uri, chunks, LOCAL_FS.as_ref(), None) |
| .await |
| .unwrap(); |
| let expected_diff = r#"@@ -3 +3,2 @@ |
| baz |
| +quux |
| "#; |
| let expected = ApplyPatchFileUpdate { |
| unified_diff: expected_diff.to_string(), |
| original_content: "foo\nbar\nbaz\n".to_string(), |
| content: "foo\nbar\nbaz\nquux\n".to_string(), |
| }; |
| assert_eq!(expected, diff); |
| } |
|
|
| #[tokio::test] |
| async fn test_unified_diff_interleaved_changes() { |
| |
| let dir = tempdir().unwrap(); |
| let path = dir.path().join("interleaved.txt"); |
| fs::write(&path, "a\nb\nc\nd\ne\nf\n").unwrap(); |
|
|
| |
| |
| let patch_body = format!( |
| r#"*** Update File: {} |
| @@ |
| a |
| -b |
| +B |
| @@ |
| d |
| -e |
| +E |
| @@ |
| f |
| +g |
| *** End of File"#, |
| path.display() |
| ); |
| let patch = wrap_patch(&patch_body); |
|
|
| |
| let parsed = parse_patch(&patch).unwrap(); |
| let chunks = match parsed.hunks.as_slice() { |
| [Hunk::UpdateFile { chunks, .. }] => chunks, |
| _ => panic!("Expected a single UpdateFile hunk"), |
| }; |
|
|
| let path_uri = PathUri::from_host_native_path(&path).expect("absolute test path"); |
| let diff = |
| unified_diff_from_chunks(&path_uri, chunks, LOCAL_FS.as_ref(), None) |
| .await |
| .unwrap(); |
|
|
| let expected_diff = r#"@@ -1,6 +1,7 @@ |
| a |
| -b |
| +B |
| c |
| d |
| -e |
| +E |
| f |
| +g |
| "#; |
|
|
| let expected = ApplyPatchFileUpdate { |
| unified_diff: expected_diff.to_string(), |
| original_content: "a\nb\nc\nd\ne\nf\n".to_string(), |
| content: "a\nB\nc\nd\nE\nf\ng\n".to_string(), |
| }; |
|
|
| assert_eq!(expected, diff); |
|
|
| let mut stdout = Vec::new(); |
| let mut stderr = Vec::new(); |
| apply_patch( |
| &patch, |
| &PathUri::from_host_native_path(dir.path()).expect("absolute test path"), |
| &mut stdout, |
| &mut stderr, |
| LOCAL_FS.as_ref(), |
| None, |
| ) |
| .await |
| .unwrap(); |
| let contents = fs::read_to_string(path).unwrap(); |
| assert_eq!( |
| contents, |
| r#"a |
| B |
| c |
| d |
| E |
| f |
| g |
| "# |
| ); |
| } |
|
|