trashclaw / tests /test_patch_file.py
elyanlabs's picture
Source snapshot of github.com/Scottcjn/trashclaw@6dce9ed
53b2ac9 verified
Raw History Blame Contribute Delete
6.55 kB
# SPDX-License-Identifier: MIT
"""Tests for tool_patch_file.
Standard unified diffs carry context lines. The old implementation ignored
them and removed lines counted from the hunk start, so a diff that changed
line 2 deleted line 1 instead and still reported success.
"""
import os
import sys
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
import trashclaw
@pytest.fixture(autouse=True)
def isolate(tmp_path, monkeypatch):
monkeypatch.setattr(trashclaw, "CWD", str(tmp_path))
monkeypatch.setattr(trashclaw, "UNDO_STACK", [])
return tmp_path
def _write(tmp_path, text, name="f.txt"):
p = tmp_path / name
p.write_text(text)
return p
def test_context_lines_are_honoured(tmp_path):
p = _write(tmp_path, "a\nb\nc\n")
patch = "--- a/f.txt\n+++ b/f.txt\n@@ -1,3 +1,3 @@\n a\n-b\n+B\n c\n"
result = trashclaw.tool_patch_file(str(p), patch)
assert result.startswith("Patched")
assert p.read_text() == "a\nB\nc\n"
def test_mismatched_hunk_is_rejected_and_file_untouched(tmp_path):
p = _write(tmp_path, "a\nb\nc\n")
result = trashclaw.tool_patch_file(str(p), "@@ -1,3 +1,3 @@\n a\n-x\n+X\n c\n")
assert result.startswith("Error")
assert "No changes written" in result
assert p.read_text() == "a\nb\nc\n"
assert trashclaw.UNDO_STACK == [] # nothing was changed, nothing to undo
def test_partial_failure_writes_nothing(tmp_path):
original = "".join(f"l{i}\n" for i in range(1, 11))
p = _write(tmp_path, original)
patch = ("@@ -1,2 +1,2 @@\n l1\n-l2\n+L2\n"
"@@ -8,2 +8,2 @@\n l8\n-nope\n+NOPE\n")
result = trashclaw.tool_patch_file(str(p), patch)
assert "hunk 2 of 2" in result
assert p.read_text() == original
def test_multiple_hunks_with_line_count_changes(tmp_path):
p = _write(tmp_path, "".join(f"l{i}\n" for i in range(1, 21)))
patch = ("@@ -1,3 +1,3 @@\n l1\n-l2\n+L2\n l3\n"
"@@ -9,3 +9,4 @@\n l9\n-l10\n+L10\n+new\n l11\n")
result = trashclaw.tool_patch_file(str(p), patch)
assert "2 hunks, 3 additions, 2 removals" in result
lines = p.read_text().splitlines()
assert lines[:3] == ["l1", "L2", "l3"]
assert lines[8:12] == ["l9", "L10", "new", "l11"]
assert len(lines) == 21
def test_drifted_line_numbers_still_apply(tmp_path):
p = _write(tmp_path, "x\ny\na\nb\nc\n")
result = trashclaw.tool_patch_file(str(p), "@@ -1,3 +1,3 @@\n a\n-b\n+B\n c\n")
assert result.startswith("Patched")
assert p.read_text() == "x\ny\na\nB\nc\n"
def test_blank_context_line_without_leading_space(tmp_path):
p = _write(tmp_path, "def f():\n\n return 1\n")
patch = "@@ -1,3 +1,3 @@\n def f():\n\n- return 1\n+ return 2\n"
result = trashclaw.tool_patch_file(str(p), patch)
assert result.startswith("Patched")
assert p.read_text() == "def f():\n\n return 2\n"
def test_pure_insertion_after_line(tmp_path):
p = _write(tmp_path, "a\nb\n")
result = trashclaw.tool_patch_file(str(p), "@@ -2,0 +3,1 @@\n+c\n")
assert result.startswith("Patched")
assert p.read_text() == "a\nb\nc\n"
def test_create_new_file(tmp_path):
p = tmp_path / "new.txt"
result = trashclaw.tool_patch_file(str(p), "--- /dev/null\n+++ b/new.txt\n@@ -0,0 +1,2 @@\n+x\n+y\n")
assert result.startswith("Patched")
assert p.read_text() == "x\ny\n"
def test_patch_without_hunks_is_an_error(tmp_path):
p = _write(tmp_path, "a\n")
result = trashclaw.tool_patch_file(str(p), "just some text")
assert result.startswith("Error")
assert p.read_text() == "a\n"
def test_successful_patch_is_undoable(tmp_path):
p = _write(tmp_path, "a\nb\n")
trashclaw.tool_patch_file(str(p), "@@ -1,2 +1,2 @@\n a\n-b\n+B\n")
assert trashclaw.UNDO_STACK[-1]["content"] == "a\nb\n"
assert trashclaw.UNDO_STACK[-1]["action"] == "patch"
# --- Review follow-ups: each case below was reported as "Patched" before. ---
def _assert_refused(p, result, original):
assert result.startswith("Error"), result
assert "No changes written" in result
assert p.read_text() == original
assert trashclaw.UNDO_STACK == []
def test_removed_and_added_lines_that_look_like_file_headers(tmp_path):
# "-- old" / "++ new" (Lua/SQL comments) show up in the diff as
# "--- old" / "+++ new"; the hunk counts say they are body lines.
p = _write(tmp_path, "a\nb\n-- x\nc\n")
result = trashclaw.tool_patch_file(str(p), "@@ -2,2 +2,2 @@\n-b\n+B\n--- x\n+++ y\n")
assert result.startswith("Patched"), result
assert p.read_text() == "a\nB\n++ y\nc\n"
def test_hunk_shorter_than_header_is_refused(tmp_path):
p = _write(tmp_path, "a\nb\nc\n")
result = trashclaw.tool_patch_file(str(p), "@@ -1,3 +1,3 @@\n a\n-b\n+B\n")
_assert_refused(p, result, "a\nb\nc\n")
assert "-3/+3" in result
def test_hunk_longer_than_header_is_refused(tmp_path):
p = _write(tmp_path, "a\nb\nc\n")
result = trashclaw.tool_patch_file(str(p), "@@ -1,2 +1,2 @@\n a\n-b\n+B\n c\n")
_assert_refused(p, result, "a\nb\nc\n")
def test_trailing_blank_context_line_is_verified(tmp_path):
p = _write(tmp_path, "a\nX\n")
result = trashclaw.tool_patch_file(str(p), "@@ -1,2 +1,2 @@\n-a\n+A\n \n")
_assert_refused(p, result, "a\nX\n")
q = _write(tmp_path, "a\n\n", name="g.txt")
assert trashclaw.tool_patch_file(str(q), "@@ -1,2 +1,2 @@\n-a\n+A\n \n").startswith("Patched")
assert q.read_text() == "A\n\n"
def test_drift_is_carried_into_later_insertion(tmp_path):
p = _write(tmp_path, "x\na\nb\n")
result = trashclaw.tool_patch_file(str(p), "@@ -1 +1 @@\n-a\n+A\n@@ -2,0 +3,1 @@\n+c\n")
assert result.startswith("Patched"), result
assert p.read_text() == "x\nA\nb\nc\n"
def test_out_of_range_insertion_is_refused(tmp_path):
p = _write(tmp_path, "a\nb\n")
result = trashclaw.tool_patch_file(str(p), "@@ -999,0 +1000,1 @@\n+z\n")
_assert_refused(p, result, "a\nb\n")
def test_insertion_after_last_line_without_newline(tmp_path):
p = _write(tmp_path, "a\nb")
result = trashclaw.tool_patch_file(str(p), "@@ -2,0 +3,1 @@\n+c\n")
assert result.startswith("Patched"), result
assert p.read_text() == "a\nb\nc\n"
def test_multi_file_diff_is_refused(tmp_path):
p = _write(tmp_path, "a\n")
patch = ("--- a/f.txt\n+++ b/f.txt\n@@ -1 +1 @@\n-a\n+A\n"
"--- a/g.txt\n+++ b/g.txt\n@@ -1 +1 @@\n-A\n+Z\n")
result = trashclaw.tool_patch_file(str(p), patch)
_assert_refused(p, result, "a\n")
assert "more than one file" in result