mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-15 07:41:20 +00:00
fix(sandbox): match local grep lines without their terminator (#8210)
* fix(sandbox): match local grep lines without their terminator find_grep_matches ran the regex on each line with its trailing "\n", so patterns that can consume the newline (\s$, [^a-z]$, \n) matched every newline-ended line while the returned line was shown stripped. Strip the LF or CRLF terminator before matching, as grep(1) and the remote sandbox providers do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(sandbox): clarify local grep CRLF handling * test(sandbox): cover literal grep line terminators --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
parent
dba950443d
commit
077773a967
@ -311,12 +311,15 @@ def find_grep_matches(
|
||||
continue
|
||||
# newline="\n" ends lines where LocalSandbox.read_file does, so a
|
||||
# hit's line_number is the start_line that reads it back; a bare
|
||||
# "\r" stays content. A CRLF terminator still reads as "\n", so $
|
||||
# anchors match as before.
|
||||
# "\r" stays content. Like native grep on Unix, local matching
|
||||
# excludes LF. LocalSandbox also strips the CR in CRLF, so $ anchors
|
||||
# at the end of the line text for both line endings.
|
||||
with file_path.open(encoding="utf-8", errors="replace", newline="\n") as handle:
|
||||
for line_number, line in enumerate(handle, start=1):
|
||||
if line.endswith("\r\n"):
|
||||
line = line[:-2] + "\n"
|
||||
line = line[:-2]
|
||||
elif line.endswith("\n"):
|
||||
line = line[:-1]
|
||||
if not literal and len(line) > _max_line_chars:
|
||||
continue
|
||||
if matches_line(line):
|
||||
|
||||
@ -15,6 +15,8 @@ from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
import deerflow.sandbox.local.local_sandbox as local_sandbox
|
||||
from deerflow.sandbox.local.local_sandbox import LocalSandbox
|
||||
from deerflow.sandbox.read_file_contract import count_file_lines
|
||||
@ -216,3 +218,23 @@ def test_read_before_write_content_changes_when_only_line_endings_change(tmp_pat
|
||||
after = read_current_file_content(runtime, "/mnt/user-data/outputs/file.txt")
|
||||
|
||||
assert before != after
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pattern", "literal", "expected"),
|
||||
[
|
||||
(r"\s$", False, [(2, "trailing ")]),
|
||||
(r"[^a-z]$", False, [(2, "trailing ")]),
|
||||
(r"\n", False, []),
|
||||
pytest.param("\n", True, [], id="literal-newline"),
|
||||
],
|
||||
)
|
||||
def test_grep_does_not_match_the_line_terminator(tmp_path, pattern, literal, expected) -> None:
|
||||
# LocalSandbox excludes LF and CRLF terminators from matching, so
|
||||
# a trailing-whitespace search only matches whitespace in the line text.
|
||||
path = tmp_path / "ws.txt"
|
||||
path.write_bytes(b"clean\ntrailing \r\nalso clean\r\nlast clean")
|
||||
|
||||
matches, _ = LocalSandbox("t").grep(str(path), pattern, literal=literal)
|
||||
|
||||
assert [(match.line_number, match.line) for match in matches] == expected
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user