llmcodegen/tests/test_diff_applier.py

200 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""
Unit tests for diff_applier.py, covering various scenarios such as new file creation,
modification of existing files, conflict handling, and error cases.
"""
import os
import sys
import tempfile
import shutil
import pytest
import git # GitPython is required; assumed installed via project dependencies
# Add src directory to path for module import
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from llm_codegen.diff_applier import parse_diff_files, apply_diff
def test_parse_diff_files():
"""Test parsing unified diff strings to extract file paths."""
# Diff with modification and new file
diff = """--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-old content
+new content
--- a/new_file.txt
+++ b/new_file.txt
@@ -0,0 +1 @@
+new file content
"""
files = parse_diff_files(diff)
assert set(files) == {'file1.txt', 'new_file.txt'}
# Diff with file deletion
diff_del = """--- a/deleted.txt
+++ /dev/null
@@ -1 +0,0 @@
-content to delete
"""
files = parse_diff_files(diff_del)
assert files == ['deleted.txt'] # Old file path is extracted
# Empty diff
assert parse_diff_files('') == []
assert parse_diff_files('\n') == []
# Diff with only new file (no old file)
diff_new_only = """--- /dev/null
+++ b/only_new.txt
@@ -0,0 +1 @@
+only new
"""
files = parse_diff_files(diff_new_only)
assert files == [] /dev/null is ignored
# Invalid diff format (should still handle gracefully)
diff_invalid = "invalid diff string"
files = parse_diff_files(diff_invalid)
assert files == [] # No valid file paths found
@pytest.fixture
def temp_git_repo():
"""Create a temporary git repository for testing apply_diff."""
temp_dir = tempfile.mkdtemp()
repo = git.Repo.init(temp_dir)
# Make an initial commit to have a clean state
init_file = os.path.join(temp_dir, 'README.md')
with open(init_file, 'w') as f:
f.write('Initial commit content')
repo.git.add('README.md')
repo.git.commit('-m', 'initial commit')
yield temp_dir, repo
# Cleanup after test
shutil.rmtree(temp_dir, ignore_errors=True)
def test_apply_diff_new_file(temp_git_repo):
"""Test applying a diff that creates a new file."""
temp_dir, repo = temp_git_repo
diff = """--- /dev/null
+++ b/test_new.txt
@@ -0,0 +1 @@
+This is a newly created file.
"""
result = apply_diff(diff, temp_dir)
assert result['success'] == True
assert 'test_new.txt' in result['applied_files']
new_file_path = os.path.join(temp_dir, 'test_new.txt')
assert os.path.exists(new_file_path)
with open(new_file_path, 'r') as f:
content = f.read()
assert content.strip() == 'This is a newly created file.'
def test_apply_diff_modify_existing_file(temp_git_repo):
"""Test applying a diff that modifies an existing file."""
temp_dir, repo = temp_git_repo
# Create an existing file and commit it
existing_file = os.path.join(temp_dir, 'existing.txt')
with open(existing_file, 'w') as f:
f.write('Original line 1\nOriginal line 2')
repo.git.add('existing.txt')
repo.git.commit('-m', 'add existing file')
diff = """--- a/existing.txt
+++ b/existing.txt
@@ -1,2 +1,2 @@
-Original line 1
+Modified line 1
Original line 2
"""
result = apply_diff(diff, temp_dir)
assert result['success'] == True
assert 'existing.txt' in result['applied_files']
with open(existing_file, 'r') as f:
content = f.read()
assert content == 'Modified line 1\nOriginal line 2'
def test_apply_diff_conflict_handling(temp_git_repo):
"""Test applying a diff that causes a conflict with uncommitted changes."""
temp_dir, repo = temp_git_repo
# Create and commit a file
conflict_file = os.path.join(temp_dir, 'conflict.txt')
with open(conflict_file, 'w') as f:
f.write('Initial line 1\nInitial line 2')
repo.git.add('conflict.txt')
repo.git.commit('-m', 'add conflict file')
# Modify the file without committing to create conflict
with open(conflict_file, 'w') as f:
f.write('Changed line 1\nInitial line 2') # Change first line
diff = """--- a/conflict.txt
+++ b/conflict.txt
@@ -1,2 +1,2 @@
-Initial line 1
+Diff line 1
Initial line 2
"""
result = apply_diff(diff, temp_dir)
assert result['success'] == False
# Check for conflict or error in message
assert 'conflict' in result['message'].lower() or 'error' in result['message'].lower()
assert result['error_details'] != ''
def test_apply_diff_empty_diff():
"""Test applying an empty diff string."""
result = apply_diff('', '.')
assert result['success'] == False
assert 'empty' in result['message'].lower()
def test_apply_diff_invalid_directory():
"""Test applying a diff to a non-existent directory."""
non_existent_dir = '/tmp/non_existent_dir_12345'
diff = """--- a/dummy.txt
+++ b/dummy.txt
@@ -1 +1 @@
-old
+new
"""
result = apply_diff(diff, non_existent_dir)
assert result['success'] == False
assert 'does not exist' in result['message'].lower()
def test_apply_diff_no_git_repo_initialization():
"""Test applying a diff to a non-git directory, which should initialize a repo."""
temp_dir = tempfile.mkdtemp()
try:
# Create a non-git directory with a file
non_git_file = os.path.join(temp_dir, 'non_git.txt')
with open(non_git_file, 'w') as f:
f.write('Pre-existing content')
diff = """--- a/non_git.txt
+++ b/non_git.txt
@@ -1 +1 @@
-Pre-existing content
+Updated content
"""
result = apply_diff(diff, temp_dir)
assert result['success'] == True
assert 'non_git.txt' in result['applied_files']
with open(non_git_file, 'r') as f:
content = f.read()
assert content == 'Updated content'
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
if __name__ == "__main__":
# Run tests if executed as script
pytest.main([__file__])