import contextlib import io import os from pathlib import Path import subprocess import unittest from unittest.mock import patch import fast_source_search as search class SearchScopeTests(unittest.TestCase): def run_search(self, arguments): selected = Path(search.__file__).resolve() unrelated = search.ROOT / "tools/run_business_acceptance.py" output = io.StringIO() with patch.dict(os.environ, {"FREECAD_SOURCE_BUDGET_MARKER": ""}), \ patch("sys.argv", ["fast_source_search.py", *arguments]), \ patch.object(search, "batch_paths", return_value=[unrelated]) as batch, \ patch.object(search, "ripgrep", return_value="rg"), \ patch.object(search.subprocess, "run", return_value=subprocess.CompletedProcess([], 0, "", "")) as run, \ contextlib.redirect_stderr(output): status = search.main() return status, run, batch, selected, unrelated def test_explicit_forms_exclude_batch(self): for arguments in [ ["needle", "--path", "tools/fast_source_search.py"], ["--pattern", "needle", "--paths", "tools/fast_source_search.py"], ["needle", "tools/fast_source_search.py"], ]: with self.subTest(arguments=arguments): status, run, batch, selected, unrelated = self.run_search(arguments) self.assertEqual(status, 0) self.assertIn(str(selected), run.call_args.args[0]) self.assertNotIn(str(unrelated), run.call_args.args[0]) batch.assert_not_called() def test_implicit_scope_keeps_batch(self): status, run, batch, _, unrelated = self.run_search(["needle"]) self.assertEqual(status, 0) self.assertIn(str(unrelated), run.call_args.args[0]) batch.assert_called_once_with() def test_missing_explicit_path_does_not_fall_back(self): status, run, batch, _, _ = self.run_search([ "needle", "--path", "tools/nonexistent-search-scope-test-file.cpp", ]) self.assertEqual(status, 2) run.assert_not_called() batch.assert_not_called() if __name__ == "__main__": unittest.main()