Files
aiturk-hermes-ide/tests/tools/test_search_files_engine_selection.py
T

540 lines
19 KiB
Python

"""Behavior tests for file-search ordering and ripgrep selection."""
import json
import re
import pytest
from tools.environments.local import LocalEnvironment
from tools.file_operations import SearchResult, ShellFileOperations
from tools.file_tools import SEARCH_FILES_SCHEMA, _handle_search_files, search_tool
class RecordingEnvironment:
is_local = False
cwd = "/repo"
def __init__(self, *, rg_output="/repo/one.py\n/repo/two.py\n", rg_code=0):
self.commands = []
self.rg_output = rg_output
self.rg_code = rg_code
def execute(self, command, **kwargs):
self.commands.append(command)
if command.startswith("test -e "):
return {"output": "exists\n", "returncode": 0}
if command.startswith("command -v rg"):
return {"output": "/opt/Rip Grep/rg\n", "returncode": 0}
if "--version" in command:
return {"output": "ripgrep 14.1.1\n", "returncode": 0}
if "--files" in command:
return {"output": self.rg_output, "returncode": self.rg_code}
return {"output": "", "returncode": 1}
@property
def rg_commands(self):
return [command for command in self.commands if "--files" in command]
def test_schema_exposes_fast_discovery_default_and_exact_modified_opt_in():
order = SEARCH_FILES_SCHEMA["parameters"]["properties"]["order"]
assert order["enum"] == ["discovery", "modified"]
assert order["default"] == "discovery"
assert "fast bounded traversal order" in order["description"]
assert "exact global newest-first" in order["description"]
assert "ignored for content" in order["description"]
def test_default_file_search_runs_one_bounded_unsorted_rg_command():
env = RecordingEnvironment()
ops = ShellFileOperations(env)
result = ops.search("*.py", path="/repo", target="files", limit=1, offset=1)
assert result.files == ["/repo/two.py"]
assert len(env.rg_commands) == 1
assert "--sortr" not in env.rg_commands[0]
assert "head -n 3" in env.rg_commands[0]
@pytest.mark.parametrize("engine", ["rg", "find"])
def test_bounded_filename_total_is_serialized_as_a_lower_bound(engine, monkeypatch):
conceptual_files = [f"/repo/file-{index:03}.py" for index in range(200)]
env = RecordingEnvironment()
def execute(command, **kwargs):
env.commands.append(command)
if command.startswith("test -e "):
return {"output": "exists\n", "returncode": 0}
if command.startswith("command -v rg"):
return {
"output": "/usr/bin/rg\n" if engine == "rg" else "",
"returncode": 0 if engine == "rg" else 1,
}
if "--files" in command or command.startswith("set -o pipefail; find "):
fetch_limit = int(re.search(r"head -n (\d+)", command).group(1))
return {
"output": "\n".join(conceptual_files[:fetch_limit]) + "\n",
"returncode": 0,
}
return {"output": "", "returncode": 1}
env.execute = execute
ops = ShellFileOperations(env)
if engine == "find":
monkeypatch.setattr(ops, "_has_command", lambda command: command == "find")
result = ops.search("*.py", path="/repo", target="files", limit=50)
serialized = result.to_dict()
assert result.total_count == 51
assert len(result.files) == 50
assert serialized["truncated"] is True
assert serialized["total_count_is_lower_bound"] is True
def test_modified_file_search_runs_one_exact_order_rg_command():
env = RecordingEnvironment()
ops = ShellFileOperations(env)
result = ops.search("*.py", path="/repo", target="files", order="modified")
assert result.error is None
assert len(env.rg_commands) == 1
assert "--sortr=modified" in env.rg_commands[0]
def test_modified_zero_match_exit_one_is_valid_without_capability_error():
env = RecordingEnvironment(rg_output="", rg_code=1)
result = ShellFileOperations(env).search(
"*.missing", path="/repo", target="files", order="modified"
)
assert result.error is None
assert result.files == []
assert len(env.rg_commands) == 1
@pytest.mark.parametrize(
"version",
[
"ripgrep 13.0.0\n",
"ripgrep unknown\n",
"ripgrep 14 garbage\n",
"ripgrep 14\n",
"ripgrep 14.1\n",
"ripgrep 14.1.1-\n",
"ripgrep 14.1.1+\n",
"ripgrep 14.1.1-alpha..1\n",
"ripgrep 14.1.1+build..2\n",
"ripgrep 14.1.1-01\n",
"ripgrep 014.1.1\n",
"ripgrep 14.01.1\n",
"ripgrep 14.1.01\n",
],
)
def test_modified_requires_parseable_ripgrep_14_before_search(version):
env = RecordingEnvironment()
def execute(command, **kwargs):
env.commands.append(command)
if command.startswith("test -e "):
return {"output": "exists\n", "returncode": 0}
if command.startswith("command -v rg"):
return {"output": "/opt/Rip Grep/rg\n", "returncode": 0}
if "--version" in command:
return {"output": version, "returncode": 0}
raise AssertionError(f"search must not run: {command}")
env.execute = execute
ops = ShellFileOperations(env)
first = ops.search("*.py", path="/repo", target="files", order="modified")
second = ops.search("*.py", path="/repo", target="files", order="modified")
assert "ripgrep 14" in (first.error or "").lower()
assert second.error == first.error
assert env.rg_commands == []
assert len([c for c in env.commands if "--version" in c]) == 1
def test_modified_accepts_complete_ripgrep_semver_with_revision_text():
env = RecordingEnvironment()
original_execute = env.execute
def execute(command, **kwargs):
if "--version" in command:
env.commands.append(command)
return {"output": "ripgrep 14.1.1 (rev abc123)\n", "returncode": 0}
return original_execute(command, **kwargs)
env.execute = execute
result = ShellFileOperations(env).search(
"*.py", path="/repo", target="files", order="modified"
)
assert result.error is None
assert len(env.rg_commands) == 1
def test_modified_accepts_ripgrep_semver_with_prerelease_and_build_metadata():
env = RecordingEnvironment()
original_execute = env.execute
def execute(command, **kwargs):
if "--version" in command:
env.commands.append(command)
return {
"output": "ripgrep 14.1.1-alpha.1+build.2\n",
"returncode": 0,
}
return original_execute(command, **kwargs)
env.execute = execute
result = ShellFileOperations(env).search(
"*.py", path="/repo", target="files", order="modified"
)
assert result.error is None
assert len(env.rg_commands) == 1
def test_empty_discovery_output_is_zero_matches_without_retry():
env = RecordingEnvironment(rg_output="", rg_code=0)
ops = ShellFileOperations(env)
result = ops.search("*.missing", path="/repo", target="files")
assert result.error is None
assert result.files == []
assert result.total_count == 0
assert len(env.rg_commands) == 1
def test_modified_capability_failure_is_actionable_and_not_downgraded():
env = RecordingEnvironment(rg_output="", rg_code=2)
ops = ShellFileOperations(env)
result = ops.search("*.py", path="/repo", target="files", order="modified")
assert len(env.rg_commands) == 1
assert result.error is not None
assert "exact modification-time order" in result.error.lower()
assert "ripgrep" in result.error
@pytest.mark.parametrize("order", ["discovery", "modified"])
def test_rg_partial_output_with_error_exit_fails_closed(order):
env = RecordingEnvironment(rg_output="/repo/partial.py\n", rg_code=2)
result = ShellFileOperations(env).search(
"*.py", path="/repo", target="files", order=order
)
assert result.error is not None
assert result.files == []
assert len(env.rg_commands) == 1
@pytest.mark.parametrize("order", ["discovery", "modified"])
def test_rg_sigpipe_is_benign_only_after_fetch_limit_paths(order):
output = "".join(f"/repo/{name}.py\n" for name in ("a", "b", "c", "d"))
env = RecordingEnvironment(rg_output=output, rg_code=141)
result = ShellFileOperations(env).search(
"*.py", path="/repo", target="files", limit=2, offset=1, order=order
)
assert result.error is None
assert result.files == ["/repo/b.py", "/repo/c.py"]
assert result.truncated is True
@pytest.mark.parametrize("order", ["discovery", "modified"])
def test_rg_sigpipe_with_fewer_than_fetch_limit_paths_fails_closed(order):
env = RecordingEnvironment(rg_output="/repo/partial.py\n", rg_code=141)
result = ShellFileOperations(env).search(
"*.py", path="/repo", target="files", limit=2, offset=1, order=order
)
assert result.error is not None
assert result.files == []
assert result.total_count == 0
def test_invalid_direct_file_order_returns_structured_error():
env = RecordingEnvironment()
ops = ShellFileOperations(env)
result = ops.search("*.py", path="/repo", target="files", order="random")
assert isinstance(result, SearchResult)
assert result.error == "Invalid file search order 'random'; expected 'discovery' or 'modified'."
assert env.rg_commands == []
def test_handler_forwards_modified_order(monkeypatch):
captured = {}
def fake_search_tool(**kwargs):
captured.update(kwargs)
return "{}"
monkeypatch.setattr("tools.file_tools.search_tool", fake_search_tool)
_handle_search_files({"pattern": "*.py", "target": "files", "order": "modified"})
assert captured["order"] == "modified"
def test_repeated_search_key_distinguishes_order(monkeypatch):
class StubOperations:
def search(self, **kwargs):
return SearchResult()
monkeypatch.setattr("tools.file_tools._get_file_ops", lambda task_id: StubOperations())
task_id = "engine-order-key"
for _ in range(3):
assert "BLOCKED" not in json.loads(
search_tool("*.py", target="files", order="discovery", task_id=task_id)
).get("error", "")
changed = json.loads(
search_tool("*.py", target="files", order="modified", task_id=task_id)
)
assert "BLOCKED" not in changed.get("error", "")
class RipgrepInvocationEnvironment(RecordingEnvironment):
def execute(self, command, **kwargs):
self.commands.append(command)
if command.startswith("test -e "):
return {"output": "exists\n", "returncode": 0}
if command.startswith("command -v rg"):
return {"output": "/opt/Rip Grep/rg\n", "returncode": 0}
if "--version" in command:
return {"output": "ripgrep 14.1.1\n", "returncode": 0}
if "--files" in command:
return {"output": "/repo/a.py\n", "returncode": 0}
if "--line-number" in command:
return {"output": "/repo/a.py:1:needle\n", "returncode": 0}
if "--count-matches" in command:
return {"output": "", "returncode": 1}
return {"output": "", "returncode": 1}
def test_resolved_executable_with_spaces_is_used_by_every_rg_invocation():
env = RipgrepInvocationEnvironment()
ops = ShellFileOperations(env)
assert ops.search("*.py", path="/repo", target="files").files
assert ops.search("needle", path="/repo", target="content").matches
assert ops._zero_match_probe("absent", "/repo", None) is None
invocations = [
command for command in env.commands
if any(flag in command for flag in ("--files", "--line-number", "--count-matches"))
]
assert invocations
assert all("'/opt/Rip Grep/rg'" in command for command in invocations)
assert all(not re.search(r"(?:^|[; ])rg\s", command) for command in invocations)
assert len([c for c in env.commands if c.startswith("command -v rg")]) == 1
def test_non_rg_command_cache_keeps_cached_misses_and_bool_values():
env = RecordingEnvironment()
ops = ShellFileOperations(env)
assert ops._has_command("find") is False
assert ops._has_command("find") is False
assert ops._command_cache == {"find": False}
assert len([c for c in env.commands if c.startswith("command -v find")]) == 1
@pytest.mark.windows_only
def test_off_path_windows_rg_miss_is_reprobed_then_success_is_cached(
tmp_path, monkeypatch
):
local_app_data = tmp_path / "Local Data"
candidate = local_app_data / "Microsoft" / "WinGet" / "Links" / "rg.exe"
monkeypatch.setenv("LOCALAPPDATA", str(local_app_data))
monkeypatch.setenv("USERPROFILE", str(tmp_path / "User Profile"))
monkeypatch.delenv("SCOOP", raising=False)
ops = ShellFileOperations(LocalEnvironment(str(tmp_path)))
probes = []
def command_v_miss(command, **kwargs):
probes.append(command)
from tools.file_operations import ExecuteResult
return ExecuteResult(stdout="", exit_code=1)
monkeypatch.setattr(ops, "_exec", command_v_miss)
assert ops._resolve_command("rg") is None
candidate.parent.mkdir(parents=True)
candidate.write_text("")
expected = str(candidate).replace("\\", "/")
assert ops._resolve_command("rg") == expected
assert ops._resolve_command("rg") == expected
assert probes == ["command -v rg 2>/dev/null", "command -v rg 2>/dev/null"]
def test_remote_resolution_never_probes_controller_host_paths(tmp_path, monkeypatch):
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path / "controller-local"))
monkeypatch.setenv("USERPROFILE", str(tmp_path / "controller-user"))
env = RecordingEnvironment()
def miss(command, **kwargs):
env.commands.append(command)
return {"output": "", "returncode": 1}
env.execute = miss
ops = ShellFileOperations(env)
assert ops._resolve_command("rg") is None
assert env.commands == ["command -v rg 2>/dev/null"]
assert str(tmp_path) not in env.commands[0]
@pytest.mark.windows_only
def test_remote_msys_shaped_executable_is_not_rewritten_as_controller_path():
env = RecordingEnvironment()
def execute(command, **kwargs):
env.commands.append(command)
if command.startswith("test -e "):
return {"output": "exists\n", "returncode": 0}
if command.startswith("command -v rg"):
return {"output": "/c/remote-tools/rg\n", "returncode": 0}
if "--files" in command:
return {"output": "/repo/a.py\n", "returncode": 0}
return {"output": "", "returncode": 1}
env.execute = execute
result = ShellFileOperations(env).search("*.py", path="/repo", target="files")
assert result.files
assert "'/c/remote-tools/rg' --files" in env.rg_commands[0]
assert "C:/remote-tools/rg" not in env.rg_commands[0]
@pytest.mark.windows_only
def test_every_windows_drive_root_is_broad_even_when_home_is_on_another_drive(
tmp_path, monkeypatch
):
import tools.file_operations as file_operations
monkeypatch.setattr(file_operations, "_HOME", "C:/Users/alice")
ops = ShellFileOperations(LocalEnvironment(str(tmp_path)))
assert ops._is_broad_local_search_root("D:/") is True
assert ops._is_broad_local_search_root("D:/repo") is False
def test_modified_multi_path_search_preserves_exact_order_request():
env = RecordingEnvironment()
def execute(command, **kwargs):
env.commands.append(command)
if command.startswith("test -e "):
if "'/one /two'" in command:
return {"output": "not_found\n", "returncode": 0}
return {"output": "exists\n", "returncode": 0}
if command.startswith("command -v rg"):
return {"output": "/usr/bin/rg\n", "returncode": 0}
if "--version" in command:
return {"output": "ripgrep 14.1.1\n", "returncode": 0}
if "--files" in command:
return {"output": "/two/new.py\n/one/old.py\n", "returncode": 0}
return {"output": "", "returncode": 1}
env.execute = execute
result = ShellFileOperations(env).search(
"*.py", path="/one /two", target="files", order="modified"
)
assert result.files == ["/two/new.py", "/one/old.py"]
assert len(env.rg_commands) == 1
assert "--sortr=modified" in env.rg_commands[0]
assert "'/one'" in env.rg_commands[0]
assert "'/two'" in env.rg_commands[0]
def test_comma_delimited_file_roots_preserve_internal_spaces_in_one_search():
env = RecordingEnvironment(rg_output="C:/root one/a.py\nC:/root two/b.py\n")
combined = "C:/root one, C:/root two"
path_checks = 0
def execute(command, **kwargs):
nonlocal path_checks
env.commands.append(command)
if command.startswith("test -e "):
path_checks += 1
output = "not_found\n" if path_checks == 1 else "exists\n"
return {"output": output, "returncode": 0}
if command.startswith("command -v rg"):
return {"output": "/usr/bin/rg\n", "returncode": 0}
if "--files" in command:
return {"output": env.rg_output, "returncode": 0}
return {"output": "", "returncode": 1}
env.execute = execute
result = ShellFileOperations(env).search(
"*.py", path=combined, target="files"
)
assert result.error is None
assert result.files == ["C:/root one/a.py", "C:/root two/b.py"]
assert len(env.rg_commands) == 1
assert "'C:/root one' 'C:/root two'" in env.rg_commands[0]
assert "path contained 2 entries" in (result.warning or "")
def test_multi_path_modified_capability_error_propagates():
env = RecordingEnvironment()
def execute(command, **kwargs):
env.commands.append(command)
if command.startswith("test -e "):
output = "not_found\n" if "'/one /two'" in command else "exists\n"
return {"output": output, "returncode": 0}
if command.startswith("command -v rg"):
return {"output": "/usr/bin/rg\n", "returncode": 0}
if "--version" in command:
return {"output": "ripgrep 13.0.0\n", "returncode": 0}
raise AssertionError(command)
env.execute = execute
result = ShellFileOperations(env).search(
"*.py", path="/one /two", target="files", order="modified"
)
assert "ripgrep 14" in (result.error or "").lower()
assert env.rg_commands == []
def test_modified_timeout_preserves_partial_results_and_limit_reason():
env = RecordingEnvironment(
rg_output="/repo/partial.py\n[Command timed out after 60s]\n",
rg_code=124,
)
result = ShellFileOperations(env).search(
"*.py", path="/repo", target="files", order="modified"
)
assert result.files == ["/repo/partial.py"]
assert result.truncated is True
assert result.limit_reason == "search_timeout"
def test_order_is_ignored_for_content_search():
env = RipgrepInvocationEnvironment()
result = ShellFileOperations(env).search(
"needle", path="/repo", target="content", order="not-a-file-order"
)
assert result.error is None
assert result.matches