| # Copyright 2026 The Chromium Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| import json | |
| import unittest | |
| from unittest.mock import patch, MagicMock, mock_open | |
| from pathlib import Path | |
| import sys | |
| import os | |
| # Mocking sys.exit to avoid test runner exit | |
| # Importing worktree | |
| import worktree | |
| class TestWorktree(unittest.TestCase): | |
| @patch("worktree.subprocess.run") | |
| def test_run_cmd(self, mock_run): | |
| worktree.run_cmd(["echo", "hi"]) | |
| mock_run.assert_called_once_with( | |
| ["echo", "hi"], cwd=None, check=True, text=True, capture_output=False | |
| ) | |
| @patch("worktree.subprocess.run") | |
| def test_run_cmd_no_check_failure(self, mock_run): | |
| import subprocess | |
| mock_run.side_effect = subprocess.CalledProcessError(1, ["false"]) | |
| result = worktree.run_cmd(["false"], check=False) | |
| self.assertIsNone(result) | |
| @patch("pathlib.Path.is_dir", return_value=True) | |
| @patch("pathlib.Path.exists", side_effect=[True, True, True]) | |
| @patch("pathlib.Path.resolve") | |
| def test_validate_parent_repo_success( | |
| self, mock_resolve, mock_exists, mock_is_dir | |
| ): | |
| mock_resolve.return_value = Path("/fake/repo") | |
| parent_dir, g_config, s_dir = worktree.validate_parent_repo("/fake/repo") | |
| self.assertEqual(parent_dir, Path("/fake/repo")) | |
| self.assertEqual(g_config, Path("/fake/repo/.gclient")) | |
| self.assertEqual(s_dir, Path("/fake/repo/src")) | |
| @patch("pathlib.Path.is_dir", return_value=True) | |
| @patch("pathlib.Path.exists", side_effect=[True, True, True]) | |
| @patch("pathlib.Path.resolve") | |
| def test_validate_parent_repo_from_src( | |
| self, mock_resolve, mock_exists, mock_is_dir | |
| ): | |
| # Simulate being called from /fake/repo/src | |
| mock_resolve.return_value = Path("/fake/repo/src") | |
| parent_dir, g_config, s_dir = worktree.validate_parent_repo( | |
| "/fake/repo/src" | |
| ) | |
| self.assertEqual(parent_dir, Path("/fake/repo")) | |
| self.assertEqual(g_config, Path("/fake/repo/.gclient")) | |
| self.assertEqual(s_dir, Path("/fake/repo/src")) | |
| @patch( | |
| "pathlib.Path.read_text", | |
| return_value='solutions = []\ncache_dir = "/tmp/cache"', | |
| ) | |
| def test_check_cache_dir_present(self, mock_read): | |
| with patch("builtins.print") as mock_print: | |
| worktree.check_cache_dir(Path("fake/.gclient")) | |
| # Should not print warning | |
| self.assertFalse( | |
| any("WARNING" in str(call) for call in mock_print.call_args_list) | |
| ) | |
| @patch("pathlib.Path.read_text", return_value='solutions = []') | |
| def test_check_cache_dir_missing(self, mock_read): | |
| with patch("builtins.print") as mock_print: | |
| worktree.check_cache_dir(Path("fake/.gclient")) | |
| # Should print warning | |
| mock_print.assert_any_call( | |
| "\nWARNING: 'cache_dir' not found in your .gclient file." | |
| ) | |
| @patch("worktree.run_cmd") | |
| def test_create_worktree(self, mock_run): | |
| worktree.create_worktree("parent/src", "variant/src", "my_branch") | |
| mock_run.assert_called_with( | |
| ["git", "worktree", "add", "variant/src", "-b", "my_branch"], | |
| cwd="parent/src", | |
| ) | |
| @patch("worktree.run_cmd") | |
| def test_create_worktree_with_base(self, mock_run): | |
| worktree.create_worktree( | |
| "parent/src", "variant/src", "my_branch", "base_branch" | |
| ) | |
| # Should call git worktree add AND git branch --set-upstream-to | |
| self.assertEqual(mock_run.call_count, 2) | |
| mock_run.assert_any_call( | |
| [ | |
| "git", | |
| "worktree", | |
| "add", | |
| "variant/src", | |
| "-b", | |
| "my_branch", | |
| "base_branch", | |
| ], | |
| cwd="parent/src", | |
| ) | |
| mock_run.assert_any_call( | |
| ["git", "branch", "--set-upstream-to", "base_branch", "my_branch"], | |
| cwd="variant/src", | |
| ) | |
| def test_setup_vscode_color(self): | |
| with ( | |
| patch("pathlib.Path.mkdir"), | |
| patch("pathlib.Path.exists", return_value=False), | |
| patch("pathlib.Path.write_text") as mock_write, | |
| ): | |
| variant_src = Path("/tmp/src") | |
| worktree.setup_vscode_color(variant_src, "red") | |
| # Check if it writes valid JSON with titleBar colors | |
| args, _ = mock_write.call_args | |
| content = json.loads(args[0]) | |
| self.assertEqual( | |
| content["workbench.colorCustomizations"]["titleBar.activeBackground"], | |
| "#3a1414", | |
| ) | |
| @patch("worktree.Path.exists") | |
| def test_get_next_variant_name(self, mock_exists): | |
| # Simulate 'bling_1' exists (True), but 'bling_2' does not (False) | |
| mock_exists.side_effect = [True, False] | |
| parent_dir = Path("/Users/vincb/bling") | |
| name = worktree.get_next_variant_name(parent_dir) | |
| self.assertEqual(name, "bling_2") | |
| @patch("worktree.validate_parent_repo") | |
| @patch("worktree.check_cache_dir") | |
| @patch("worktree.Path.mkdir") | |
| @patch("worktree.create_worktree") | |
| @patch("shutil.copy2") | |
| @patch("worktree.run_cmd") | |
| @patch("worktree.Path.exists") | |
| def test_main_internal_base( | |
| self, | |
| mock_exists, | |
| mock_run, | |
| mock_copy, | |
| mock_create, | |
| mock_mkdir, | |
| mock_check, | |
| mock_validate, | |
| ): | |
| # Mocking validate_parent_repo | |
| parent_dir = Path("/fake/repo") | |
| gclient_config = parent_dir / ".gclient" | |
| parent_src = parent_dir / "src" | |
| mock_validate.return_value = (parent_dir, gclient_config, parent_src) | |
| mock_check.return_value = True | |
| # Mocking variant setup | |
| # 1. get_next_variant_name (will be called in main) | |
| # 3. internal_src.exists() (will be True) | |
| mock_exists.side_effect = [False, False, True] | |
| # Mocking args | |
| with patch( | |
| "sys.argv", | |
| ["worktree.py", "/fake/repo", "--internal-base", "internal_base_branch"], | |
| ): | |
| worktree.main() | |
| # Verify internal logic | |
| # run_cmd should be called for: | |
| # 1. gclient sync | |
| # 2. git checkout -b ... (internal) | |
| # 3. git branch --set-upstream-to ... (internal) | |
| # Find the index of internal commands in mock_run.call_args_list | |
| calls = [call.args[0] for call in mock_run.call_args_list] | |
| self.assertIn( | |
| ["git", "checkout", "-b", "repo_1", "internal_base_branch"], calls | |
| ) | |
| self.assertIn( | |
| [ | |
| "git", | |
| "branch", | |
| "--set-upstream-to", | |
| "internal_base_branch", | |
| "repo_1", | |
| ], | |
| calls, | |
| ) | |
| @patch("worktree.validate_parent_repo") | |
| @patch("worktree.check_cache_dir") | |
| @patch("worktree.Path.mkdir") | |
| @patch("worktree.create_worktree") | |
| @patch("shutil.copy2") | |
| @patch("worktree.run_cmd") | |
| @patch("worktree.Path.exists") | |
| def test_main_internal_base_fetch_from_parent( | |
| self, | |
| mock_exists, | |
| mock_run, | |
| mock_copy, | |
| mock_create, | |
| mock_mkdir, | |
| mock_check, | |
| mock_validate, | |
| ): | |
| # Mocking validate_parent_repo | |
| parent_dir = Path("/fake/repo") | |
| gclient_config = parent_dir / ".gclient" | |
| parent_src = parent_dir / "src" | |
| mock_validate.return_value = (parent_dir, gclient_config, parent_src) | |
| mock_check.return_value = True | |
| # Mocking variant setup | |
| # 1. get_next_variant_name (will be called in main) | |
| # 2. variant_root.exists() (will be False) | |
| # 3. internal_src.exists() (will be True) | |
| mock_exists.side_effect = [False, False, True] | |
| # Mocking run_cmd: | |
| # 1. validate_parent_repo might call run_cmd (it doesn't in our mocks) | |
| # 2. git show-ref --verify refs/heads/internal_base_branch (return None to simulate missing) | |
| # 3. git remote add parent ... | |
| # 4. git fetch parent ... | |
| # 5. git checkout -b ... | |
| # 6. git branch --set-upstream-to ... | |
| mock_run.side_effect = [ | |
| None, # git show-ref returns None (captured=True) | |
| None, # git remote add | |
| None, # git fetch | |
| None, # git checkout | |
| None, # git branch --set-upstream-to | |
| ] | |
| # Mocking args | |
| with patch( | |
| "sys.argv", | |
| [ | |
| "worktree.py", | |
| "/fake/repo", | |
| "--internal-base", | |
| "internal_base_branch", | |
| ], | |
| ): | |
| worktree.main() | |
| # Verify ios_internal logic | |
| calls = [call.args[0] for call in mock_run.call_args_list] | |
| self.assertIn( | |
| ["git", "remote", "add", "parent", str(parent_src / "internal")], | |
| calls, | |
| ) | |
| self.assertIn(["git", "fetch", "parent", "internal_base_branch"], calls) | |
| self.assertIn(["git", "checkout", "-b", "repo_1", "FETCH_HEAD"], calls) | |
| self.assertIn( | |
| [ | |
| "git", | |
| "branch", | |
| "--set-upstream-to", | |
| "parent/internal_base_branch", | |
| "repo_1", | |
| ], | |
| calls, | |
| ) | |
| if __name__ == "__main__": | |
| unittest.main() |