Files
wehub-resource-sync 0418dc5cf9
CI / Python tests (3.10) (push) Has been cancelled
CI / Python tests (3.11) (push) Has been cancelled
CI / Python quality (push) Has been cancelled
CI / Frontend typecheck (push) Has been cancelled
Autopilot Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:45:00 +08:00

194 lines
5.8 KiB
Python

import asyncio
from pathlib import Path
import pytest
from openharness.tools.grep_tool import GrepTool, GrepToolInput
class _FakeStdout:
async def readline(self):
await asyncio.sleep(60)
return b""
class _ValueErrorThenEofStdout:
def __init__(self):
self.calls = 0
async def readline(self):
self.calls += 1
if self.calls == 1:
raise ValueError("Separator is not found, and chunk exceed the limit")
return b""
class _FakeProcess:
def __init__(self, stdout=None):
self.stdout = stdout or _FakeStdout()
self.stderr = None
self.returncode = None
self.terminated = False
self.killed = False
def terminate(self):
self.terminated = True
self.returncode = -15
def kill(self):
self.killed = True
self.returncode = -9
async def wait(self):
return self.returncode
@pytest.mark.asyncio
async def test_grep_tool_returns_timeout_error(monkeypatch, tmp_path: Path):
tool = GrepTool()
monkeypatch.setattr("openharness.tools.grep_tool.shutil.which", lambda _: "/usr/bin/rg")
fake_process = _FakeProcess()
async def fake_create_subprocess_exec(*args, **kwargs):
return fake_process
monkeypatch.setattr(
"openharness.tools.grep_tool.asyncio.create_subprocess_exec",
fake_create_subprocess_exec,
)
result = await tool.execute(
GrepToolInput(pattern="foo", timeout_seconds=1),
type("Ctx", (), {"cwd": tmp_path})(),
)
assert result.is_error is True
assert "grep timed out after 1 seconds" in result.output
assert fake_process.terminated or fake_process.killed
@pytest.mark.asyncio
async def test_grep_tool_uses_large_stream_limit_and_skips_valueerror(monkeypatch, tmp_path: Path):
tool = GrepTool()
monkeypatch.setattr("openharness.tools.grep_tool.shutil.which", lambda _: "/usr/bin/rg")
fake_process = _FakeProcess(stdout=_ValueErrorThenEofStdout())
seen_kwargs = {}
async def fake_create_subprocess_exec(*args, **kwargs):
seen_kwargs.update(kwargs)
fake_process.returncode = 1
return fake_process
monkeypatch.setattr(
"openharness.tools.grep_tool.asyncio.create_subprocess_exec",
fake_create_subprocess_exec,
)
result = await tool.execute(
GrepToolInput(pattern="foo"),
type("Ctx", (), {"cwd": tmp_path})(),
)
assert result.is_error is False
assert result.output == "(no matches)"
assert seen_kwargs["limit"] == 8 * 1024 * 1024
@pytest.mark.asyncio
async def test_grep_tool_discards_rg_stderr_for_directory_search(monkeypatch, tmp_path: Path):
tool = GrepTool()
monkeypatch.setattr("openharness.tools.grep_tool.shutil.which", lambda _: "/usr/bin/rg")
fake_process = _FakeProcess(stdout=_ValueErrorThenEofStdout())
seen_kwargs = {}
async def fake_create_subprocess_exec(*args, **kwargs):
seen_kwargs.update(kwargs)
fake_process.returncode = 1
return fake_process
monkeypatch.setattr(
"openharness.tools.grep_tool.asyncio.create_subprocess_exec",
fake_create_subprocess_exec,
)
result = await tool.execute(
GrepToolInput(pattern="foo"),
type("Ctx", (), {"cwd": tmp_path})(),
)
assert result.is_error is False
assert seen_kwargs["stderr"] is asyncio.subprocess.DEVNULL
@pytest.mark.asyncio
async def test_grep_tool_discards_rg_stderr_for_file_search(monkeypatch, tmp_path: Path):
tool = GrepTool()
monkeypatch.setattr("openharness.tools.grep_tool.shutil.which", lambda _: "/usr/bin/rg")
file_path = tmp_path / "notes.txt"
file_path.write_text("hello\n", encoding="utf-8")
fake_process = _FakeProcess(stdout=_ValueErrorThenEofStdout())
seen_kwargs = {}
async def fake_create_subprocess_exec(*args, **kwargs):
seen_kwargs.update(kwargs)
fake_process.returncode = 1
return fake_process
monkeypatch.setattr(
"openharness.tools.grep_tool.asyncio.create_subprocess_exec",
fake_create_subprocess_exec,
)
result = await tool.execute(
GrepToolInput(pattern="foo", root=str(file_path)),
type("Ctx", (), {"cwd": tmp_path})(),
)
assert result.is_error is False
assert seen_kwargs["stderr"] is asyncio.subprocess.DEVNULL
@pytest.mark.asyncio
async def test_grep_tool_python_fallback_reports_invalid_regex(monkeypatch, tmp_path: Path):
tool = GrepTool()
monkeypatch.setattr("openharness.tools.grep_tool.shutil.which", lambda _: None)
file_path = tmp_path / "notes.txt"
file_path.write_text("hello\n", encoding="utf-8")
result = await tool.execute(
GrepToolInput(pattern="hello(", root=str(file_path)),
type("Ctx", (), {"cwd": tmp_path})(),
)
assert result.is_error is False
assert "invalid regex pattern 'hello('" in result.output
assert "unterminated subpattern" in result.output
@pytest.mark.asyncio
async def test_grep_tool_reports_missing_root_before_spawning_rg(monkeypatch, tmp_path: Path):
tool = GrepTool()
src_root = tmp_path / "src"
tests_root = tmp_path / "tests"
src_root.mkdir()
tests_root.mkdir()
monkeypatch.setattr("openharness.tools.grep_tool.shutil.which", lambda _: "/usr/bin/rg")
async def fail_create_subprocess_exec(*args, **kwargs):
del args, kwargs
raise AssertionError("rg should not be spawned for a missing root")
monkeypatch.setattr(
"openharness.tools.grep_tool.asyncio.create_subprocess_exec",
fail_create_subprocess_exec,
)
result = await tool.execute(
GrepToolInput(pattern="continue", root=f"{src_root} {tests_root}"),
type("Ctx", (), {"cwd": tmp_path})(),
)
assert result.is_error is True
assert "Search root does not exist" in result.output
assert "call grep separately for each root" in result.output