| #!/usr/bin/env fuchsia-vendored-python | |
| # Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Unit tests for path and directory discovery logic.""" | |
| import pathlib | |
| import subprocess | |
| import unittest | |
| from unittest import mock | |
| from agents.lib import paths | |
| from agents_testing.base import BaseTestCase | |
| class PathsTest(BaseTestCase): | |
| """Tests for filesystem paths and directory discovery functions.""" | |
| def setUp(self) -> None: | |
| super().setUp() | |
| self.mock_root = self.test_dir | |
| def test_find_fuchsia_dir_from_tree_jiri_root(self) -> None: | |
| fake_root = self.mock_root / "workspace_jiri" | |
| (fake_root / ".jiri_root").mkdir(parents=True) | |
| sub_file = fake_root / "tools" / "agents" / "lib" / "paths.py" | |
| sub_file.parent.mkdir(parents=True) | |
| sub_file.write_text("# placeholder", encoding="utf-8") | |
| self.patch_object(paths, "__file__", str(sub_file)) | |
| self.patch_environ(clear=True) | |
| found = paths.find_fuchsia_dir() | |
| self.assertEqual(found, fake_root) | |
| def test_find_fuchsia_dir_from_tree_fx_root(self) -> None: | |
| fake_root = self.mock_root / "workspace_fx" | |
| fake_root.mkdir(parents=True) | |
| (fake_root / ".fx-root").touch() | |
| sub_file = fake_root / "tools" / "agents" / "lib" / "paths.py" | |
| sub_file.parent.mkdir(parents=True) | |
| sub_file.write_text("# placeholder", encoding="utf-8") | |
| self.patch_object(paths, "__file__", str(sub_file)) | |
| self.patch_environ(clear=True) | |
| found = paths.find_fuchsia_dir() | |
| self.assertEqual(found, fake_root) | |
| def test_find_fuchsia_dir_with_start_dir_jiri_root(self) -> None: | |
| fake_root = self.mock_root / "workspace_start" | |
| (fake_root / ".jiri_root").mkdir(parents=True) | |
| nested_dir = fake_root / "a" / "b" / "c" | |
| nested_dir.mkdir(parents=True) | |
| found = paths.find_fuchsia_dir(start_dir=nested_dir) | |
| self.assertEqual(found, fake_root) | |
| def test_find_fuchsia_dir_with_start_dir_fx_root(self) -> None: | |
| fake_root = self.mock_root / "workspace_start_fx" | |
| fake_root.mkdir(parents=True) | |
| (fake_root / ".fx-root").touch() | |
| nested_dir = fake_root / "x" / "y" / "z" | |
| nested_dir.mkdir(parents=True) | |
| found = paths.find_fuchsia_dir(start_dir=nested_dir) | |
| self.assertEqual(found, fake_root) | |
| def test_find_fuchsia_dir_start_dir_miss_raises(self) -> None: | |
| empty_dir = self.mock_root / "empty_dir" | |
| empty_dir.mkdir(parents=True) | |
| fake_root = self.mock_root / "workspace_file" | |
| (fake_root / ".jiri_root").mkdir(parents=True) | |
| sub_file = fake_root / "tools" / "agents" / "lib" / "paths.py" | |
| sub_file.parent.mkdir(parents=True) | |
| sub_file.write_text("# placeholder", encoding="utf-8") | |
| self.patch_object(paths, "__file__", str(sub_file)) | |
| self.patch_environ(FUCHSIA_DIR=str(fake_root)) | |
| with self.assertRaises(RuntimeError) as ctx: | |
| paths.find_fuchsia_dir(start_dir=empty_dir) | |
| self.assertIn( | |
| f"Could not locate Fuchsia root directory from start directory: {empty_dir}", | |
| str(ctx.exception), | |
| ) | |
| def test_find_fuchsia_dir_env_precedence_over_file(self) -> None: | |
| fake_root = self.mock_root / "workspace_file" | |
| (fake_root / ".jiri_root").mkdir(parents=True) | |
| sub_file = fake_root / "tools" / "agents" / "lib" / "paths.py" | |
| sub_file.parent.mkdir(parents=True) | |
| sub_file.write_text("# placeholder", encoding="utf-8") | |
| target_root = self.mock_root / "env_root" | |
| target_root.mkdir(parents=True) | |
| (target_root / ".jiri_root").mkdir(parents=True) | |
| self.patch_object(paths, "__file__", str(sub_file)) | |
| self.patch_environ(FUCHSIA_DIR=str(target_root)) | |
| found = paths.find_fuchsia_dir() | |
| self.assertEqual(found, target_root) | |
| def test_find_fuchsia_dir_ignores_env_without_root_marker(self) -> None: | |
| fake_root = self.mock_root / "workspace_file" | |
| (fake_root / ".jiri_root").mkdir(parents=True) | |
| sub_file = fake_root / "tools" / "agents" / "lib" / "paths.py" | |
| sub_file.parent.mkdir(parents=True) | |
| sub_file.write_text("# placeholder", encoding="utf-8") | |
| # invalid_env lacks .jiri_root and .fx-root | |
| invalid_env = self.mock_root / "invalid_env" | |
| invalid_env.mkdir(parents=True) | |
| self.patch_object(paths, "__file__", str(sub_file)) | |
| self.patch_environ(FUCHSIA_DIR=str(invalid_env)) | |
| # Should ignore FUCHSIA_DIR and resolve from __file__ | |
| found = paths.find_fuchsia_dir() | |
| self.assertEqual(found, fake_root) | |
| def test_find_fuchsia_dir_invalid_env_not_found_raises(self) -> None: | |
| outside_file = self.mock_root / "outside" / "script.py" | |
| outside_file.parent.mkdir(parents=True) | |
| outside_file.write_text("# placeholder", encoding="utf-8") | |
| invalid_env = self.mock_root / "invalid_env" | |
| invalid_env.mkdir(parents=True) | |
| self.patch_object(paths, "__file__", str(outside_file)) | |
| self.patch_environ(FUCHSIA_DIR=str(invalid_env)) | |
| with self.assertRaises(RuntimeError) as ctx: | |
| paths.find_fuchsia_dir() | |
| self.assertIn( | |
| "Could not locate Fuchsia root directory", | |
| str(ctx.exception), | |
| ) | |
| def test_find_fuchsia_dir_not_found_raises(self) -> None: | |
| outside_file = self.mock_root / "outside" / "script.py" | |
| outside_file.parent.mkdir(parents=True) | |
| outside_file.write_text("# placeholder", encoding="utf-8") | |
| self.patch_object(paths, "__file__", str(outside_file)) | |
| self.patch_environ(clear=True) | |
| with self.assertRaises(RuntimeError) as ctx: | |
| paths.find_fuchsia_dir() | |
| self.assertIn( | |
| "Could not locate Fuchsia root directory", | |
| str(ctx.exception), | |
| ) | |
| def test_find_config_dirs(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| public_cfg = fuchsia_dir / ".agents" / "config" | |
| public_cfg.mkdir(parents=True, exist_ok=True) | |
| vendor_google = fuchsia_dir / "vendor" / "google" / ".agents" / "config" | |
| vendor_google.mkdir(parents=True, exist_ok=True) | |
| vendor_other = fuchsia_dir / "vendor" / "other" / ".agents" / "config" | |
| vendor_other.mkdir(parents=True, exist_ok=True) | |
| # Directory without .agents/config should not be included | |
| (fuchsia_dir / "vendor" / "novendor").mkdir(parents=True, exist_ok=True) | |
| config_dirs = paths.find_config_dirs(fuchsia_dir) | |
| self.assertEqual( | |
| config_dirs, | |
| [public_cfg, vendor_google, vendor_other], | |
| ) | |
| def test_find_config_dirs_no_vendor(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| public_cfg = fuchsia_dir / ".agents" / "config" | |
| public_cfg.mkdir(parents=True, exist_ok=True) | |
| config_dirs = paths.find_config_dirs(fuchsia_dir) | |
| self.assertEqual(config_dirs, [public_cfg]) | |
| def test_find_config_dirs_no_root_config(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| vendor_google = fuchsia_dir / "vendor" / "google" / ".agents" / "config" | |
| vendor_google.mkdir(parents=True, exist_ok=True) | |
| # Root .agents/config does not exist; only vendor config should be returned | |
| config_dirs = paths.find_config_dirs(fuchsia_dir) | |
| self.assertEqual(config_dirs, [vendor_google]) | |
| def test_find_config_dirs_empty(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| config_dirs = paths.find_config_dirs(fuchsia_dir) | |
| self.assertEqual(config_dirs, []) | |
| def test_find_permission_dirs(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| public_perm = fuchsia_dir / ".agents" / "config" / "permissions" | |
| public_perm.mkdir(parents=True, exist_ok=True) | |
| vendor_perm = ( | |
| fuchsia_dir | |
| / "vendor" | |
| / "google" | |
| / ".agents" | |
| / "config" | |
| / "permissions" | |
| ) | |
| vendor_perm.mkdir(parents=True, exist_ok=True) | |
| # Vendor config without permissions directory | |
| vendor_no_perm = fuchsia_dir / "vendor" / "other" / ".agents" / "config" | |
| vendor_no_perm.mkdir(parents=True, exist_ok=True) | |
| perm_dirs = paths.find_permission_dirs(fuchsia_dir) | |
| self.assertEqual(perm_dirs, [public_perm, vendor_perm]) | |
| def test_find_permission_dirs_empty(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| public_cfg = fuchsia_dir / ".agents" / "config" | |
| public_cfg.mkdir(parents=True, exist_ok=True) | |
| perm_dirs = paths.find_permission_dirs(fuchsia_dir) | |
| self.assertEqual(perm_dirs, []) | |
| def test_find_checkout_git_repos_discovers_all(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| (fuchsia_dir / ".git").mkdir(parents=True, exist_ok=True) | |
| vendor_google = fuchsia_dir / "vendor" / "google" | |
| (vendor_google / ".git").mkdir(parents=True, exist_ok=True) | |
| vendor_partner = fuchsia_dir / "vendor" / "partner" | |
| (vendor_partner / ".git").mkdir(parents=True, exist_ok=True) | |
| integration_dir = fuchsia_dir / "integration" | |
| (integration_dir / ".git").mkdir(parents=True, exist_ok=True) | |
| repos = paths.find_checkout_git_repos(fuchsia_dir) | |
| self.assertEqual( | |
| repos, | |
| [fuchsia_dir, vendor_google, vendor_partner, integration_dir], | |
| ) | |
| def test_find_checkout_git_repos_root_only(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| (fuchsia_dir / ".git").mkdir(parents=True, exist_ok=True) | |
| repos = paths.find_checkout_git_repos(fuchsia_dir) | |
| self.assertEqual(repos, [fuchsia_dir]) | |
| def test_find_checkout_git_repos_discovers_vendor_and_integration( | |
| self, | |
| ) -> None: | |
| fuchsia_dir = self.mock_root | |
| vendor_google = fuchsia_dir / "vendor" / "google" | |
| (vendor_google / ".git").mkdir(parents=True, exist_ok=True) | |
| vendor_partner = fuchsia_dir / "vendor" / "partner" | |
| (vendor_partner / ".git").mkdir(parents=True, exist_ok=True) | |
| integration_dir = fuchsia_dir / "integration" | |
| (integration_dir / ".git").mkdir(parents=True, exist_ok=True) | |
| repos = paths.find_checkout_git_repos(fuchsia_dir) | |
| self.assertEqual( | |
| repos, | |
| [vendor_google, vendor_partner, integration_dir], | |
| ) | |
| def test_find_checkout_git_repos_ignores_non_git_dirs(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| (fuchsia_dir / "vendor" / "novendor").mkdir(parents=True, exist_ok=True) | |
| (fuchsia_dir / "vendor" / "README.md").write_text( | |
| "info", encoding="utf-8" | |
| ) | |
| (fuchsia_dir / "integration").mkdir(parents=True, exist_ok=True) | |
| (fuchsia_dir / "src").mkdir(parents=True, exist_ok=True) | |
| repos = paths.find_checkout_git_repos(fuchsia_dir) | |
| self.assertEqual(repos, []) | |
| def test_find_checkout_git_repos_git_file(self) -> None: | |
| fuchsia_dir = self.mock_root | |
| (fuchsia_dir / ".git").touch() | |
| vendor_google = fuchsia_dir / "vendor" / "google" | |
| vendor_google.mkdir(parents=True, exist_ok=True) | |
| (vendor_google / ".git").touch() | |
| integration_dir = fuchsia_dir / "integration" | |
| integration_dir.mkdir(parents=True, exist_ok=True) | |
| (integration_dir / ".git").touch() | |
| repos = paths.find_checkout_git_repos(fuchsia_dir) | |
| self.assertEqual( | |
| repos, | |
| [fuchsia_dir, vendor_google, integration_dir], | |
| ) | |
| @mock.patch("subprocess.run") | |
| def test_git_rev_parse_success(self, mock_run: mock.MagicMock) -> None: | |
| mock_run.return_value = subprocess.CompletedProcess( | |
| args=["git", "rev-parse", "--git-dir"], | |
| returncode=0, | |
| stdout=".git\n", | |
| stderr="", | |
| ) | |
| res = paths._git_rev_parse(self.mock_root, "--git-dir") | |
| self.assertEqual(res, ".git") | |
| mock_run.assert_called_once_with( | |
| ["git", "rev-parse", "--git-dir"], | |
| cwd=self.mock_root, | |
| capture_output=True, | |
| text=True, | |
| ) | |
| @mock.patch("subprocess.run") | |
| def test_git_rev_parse_failure(self, mock_run: mock.MagicMock) -> None: | |
| mock_run.return_value = subprocess.CompletedProcess( | |
| args=["git", "rev-parse", "--git-dir"], | |
| returncode=128, | |
| stdout="", | |
| stderr="fatal: not a git repo", | |
| ) | |
| with self.assertRaises(RuntimeError) as ctx: | |
| paths._git_rev_parse(self.mock_root, "--git-dir") | |
| self.assertIn( | |
| "Directory is not in a git repository", str(ctx.exception) | |
| ) | |
| @mock.patch.object(paths, "_git_rev_parse") | |
| def test_get_git_dir_and_hooks_dir( | |
| self, mock_rev_parse: mock.MagicMock | |
| ) -> None: | |
| repo_dir = self.mock_root / "real_repo" | |
| repo_dir.mkdir() | |
| def side_effect(cwd: pathlib.Path, *args: str) -> str: | |
| if args == ("--git-dir",): | |
| return ".git" | |
| if args == ("--git-path", "hooks"): | |
| return ".git/hooks" | |
| raise RuntimeError(f"Unexpected args: {args}") | |
| mock_rev_parse.side_effect = side_effect | |
| git_dir = paths.get_git_dir(repo_dir) | |
| hooks_dir = paths.get_hooks_dir(repo_dir) | |
| self.assertEqual(git_dir, (repo_dir / ".git").resolve()) | |
| self.assertEqual(hooks_dir, (repo_dir / ".git" / "hooks").resolve()) | |
| @mock.patch.object(paths, "_git_rev_parse") | |
| def test_get_git_dir_and_hooks_dir_worktree( | |
| self, mock_rev_parse: mock.MagicMock | |
| ) -> None: | |
| repo_dir = self.mock_root / "real_repo_wt" | |
| repo_dir.mkdir() | |
| wt_dir = self.mock_root / "worktree" | |
| wt_dir.mkdir() | |
| wt_git_dir = repo_dir / ".git" / "worktrees" / "wt" | |
| common_hooks = repo_dir / ".git" / "hooks" | |
| def side_effect(cwd: pathlib.Path, *args: str) -> str: | |
| if args == ("--git-dir",): | |
| return str(wt_git_dir) | |
| if args == ("--git-path", "hooks"): | |
| return str(common_hooks) | |
| raise RuntimeError(f"Unexpected args: {args}") | |
| mock_rev_parse.side_effect = side_effect | |
| git_dir = paths.get_git_dir(wt_dir) | |
| hooks_dir = paths.get_hooks_dir(wt_dir) | |
| self.assertTrue(git_dir.is_absolute()) | |
| self.assertEqual(git_dir, wt_git_dir.resolve()) | |
| self.assertEqual(hooks_dir, common_hooks.resolve()) | |
| @mock.patch.object(paths, "_git_rev_parse") | |
| def test_get_git_dir_and_hooks_dir_non_repo( | |
| self, mock_rev_parse: mock.MagicMock | |
| ) -> None: | |
| non_repo = self.mock_root / "non_repo" | |
| non_repo.mkdir() | |
| mock_rev_parse.side_effect = RuntimeError( | |
| f"Directory is not in a git repository: {non_repo}" | |
| ) | |
| with self.assertRaises(RuntimeError) as ctx: | |
| paths.get_git_dir(non_repo) | |
| self.assertIn( | |
| "Directory is not in a git repository", str(ctx.exception) | |
| ) | |
| with self.assertRaises(RuntimeError) as ctx2: | |
| paths.get_hooks_dir(non_repo) | |
| self.assertIn( | |
| "Directory is not in a git repository", str(ctx2.exception) | |
| ) | |
| @mock.patch.object(paths, "_git_rev_parse") | |
| def test_get_repo_root(self, mock_rev_parse: mock.MagicMock) -> None: | |
| repo_dir = self.mock_root / "real_repo_for_root" | |
| repo_dir.mkdir() | |
| sub_dir = repo_dir / "subdir" / "nested" | |
| sub_dir.mkdir(parents=True) | |
| mock_rev_parse.return_value = str(repo_dir) | |
| self.assertEqual(paths.get_repo_root(sub_dir), repo_dir.resolve()) | |
| mock_rev_parse.assert_called_once_with(sub_dir, "--show-toplevel") | |
| @mock.patch.object(paths, "_git_rev_parse") | |
| def test_get_repo_root_not_in_repo( | |
| self, mock_rev_parse: mock.MagicMock | |
| ) -> None: | |
| non_repo = self.mock_root / "non_repo_root" | |
| non_repo.mkdir() | |
| mock_rev_parse.side_effect = RuntimeError( | |
| f"Directory is not in a git repository: {non_repo}" | |
| ) | |
| with self.assertRaises(RuntimeError) as ctx: | |
| paths.get_repo_root(non_repo) | |
| self.assertIn( | |
| "Directory is not in a git repository", str(ctx.exception) | |
| ) | |
| if __name__ == "__main__": | |
| unittest.main() |