200 lines
5.9 KiB
Python
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__])
|