blob: efd6f530f0d9a4cebf7f01cae37fab0bca30c31d [file]
#!/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."""
from __future__ import annotations
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()