"""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