| # Copyright 2025 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. |
| |
| """Tests for workspace.""" |
| |
| import os |
| import subprocess |
| import threading |
| import time |
| import unittest |
| from pathlib import Path |
| from unittest.mock import MagicMock, PropertyMock, patch |
| |
| import cartfs |
| import mock_fs |
| import workspace |
| |
| |
| class TestCogMetadata(unittest.TestCase): |
| """Tests for CogMetadata.""" |
| |
| def test_to_dict(self) -> None: |
| """Test that the dictionary representation is correct.""" |
| metadata = workspace.CogMetadata( |
| workspace_name="test-ws", repo_name="fuchsia", workspace_id="ws-id" |
| ) |
| self.assertEqual( |
| metadata.to_dict(), |
| { |
| "workspace_name": "test-ws", |
| "repo_name": "fuchsia", |
| "workspace_id": "ws-id", |
| }, |
| ) |
| |
| def test_from_file_success(self) -> None: |
| """Test that metadata can be loaded from a file.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| metadata_path = ( |
| fs.cartfs_dir / "test-ws" / workspace.COG_METADATA_FILE_NAME |
| ) |
| metadata_path.parent.mkdir(exist_ok=True, parents=True) |
| with open(metadata_path, "w") as f: |
| f.write( |
| '{"workspace_name": "test-ws", "repo_name": "fuchsia", "workspace_id": "ws-id"}' |
| ) |
| |
| metadata = workspace.CogMetadata.from_file(metadata_path) |
| self.assertIsNotNone(metadata) |
| |
| # This assert is needed to make mypy happy |
| assert metadata is not None |
| self.assertEqual(metadata.workspace_name, "test-ws") |
| self.assertEqual(metadata.repo_name, "fuchsia") |
| self.assertEqual(metadata.workspace_id, "ws-id") |
| |
| def test_from_file_not_found(self) -> None: |
| """Test that None is returned when the file does not exist.""" |
| with mock_fs.FileSystemTestHelper(): |
| metadata = workspace.CogMetadata.from_file( |
| Path("non-existent-file") |
| ) |
| self.assertIsNone(metadata) |
| |
| def test_from_file_invalid_json(self) -> None: |
| """Test that None is returned when the file contains invalid JSON.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| metadata_path = ( |
| fs.cartfs_dir / "test-ws" / workspace.COG_METADATA_FILE_NAME |
| ) |
| metadata_path.parent.mkdir(exist_ok=True, parents=True) |
| with open(metadata_path, "w") as f: |
| f.write("invalid-json") |
| metadata = workspace.CogMetadata.from_file(metadata_path) |
| self.assertIsNone(metadata) |
| |
| def test_from_file_missing_keys(self) -> None: |
| """Test that None is returned when the file is missing keys.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| metadata_path = ( |
| fs.cartfs_dir / "test-ws" / workspace.COG_METADATA_FILE_NAME |
| ) |
| metadata_path.parent.mkdir(exist_ok=True, parents=True) |
| with open(metadata_path, "w") as f: |
| f.write('{"workspace_name": "test-ws"}') |
| metadata = workspace.CogMetadata.from_file(metadata_path) |
| self.assertIsNone(metadata) |
| |
| def test_write(self) -> None: |
| """Test that metadata can be written to a file.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| metadata = workspace.CogMetadata( |
| workspace_name="test-ws", |
| repo_name="fuchsia", |
| workspace_id="ws-id", |
| ) |
| metadata.write(fs.cartfs_dir) |
| self.assertEqual( |
| fs.read( |
| workspace.COG_METADATA_FILE_NAME, mock_fs.FSType.CARTFS |
| ), |
| '{\n "workspace_name": "test-ws",\n "repo_name": "fuchsia",\n "workspace_id": "ws-id"\n}', |
| ) |
| |
| |
| class TestCartfs(unittest.TestCase): |
| """Tests for Cartfs.""" |
| |
| def test_suggest_cartfs_dir_name_handles_collision(self) -> None: |
| """Test that a counter is appended when the directory exists.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| with patch.object( |
| cartfs.Cartfs, "find_mount_point", return_value=fs.cartfs_dir |
| ): |
| c = cartfs.Cartfs() |
| (fs.cartfs_dir / "test-ws").mkdir() |
| suggested_name = c.suggest_cartfs_dir_name(base_name="test-ws") |
| self.assertEqual(str(suggested_name), "test-ws-1") |
| |
| |
| class TestWorkspace(unittest.TestCase): |
| """Tests for Workspace.""" |
| |
| def setUp(self) -> None: |
| self.fs = mock_fs.FileSystemTestHelper() |
| self.patcher = patch.object( |
| cartfs.Cartfs, "find_mount_point", return_value=self.fs.cartfs_dir |
| ) |
| self.patcher.start() |
| |
| def tearDown(self) -> None: |
| self.patcher.stop() |
| self.fs.cleanup() |
| |
| def test_config_success(self) -> None: |
| """Test that config reads from file.""" |
| workspace_dir = self.fs.cog_dir / "testuser" / "test-workspace" |
| repo_name = "fuchsia" |
| config_path = ( |
| workspace_dir / repo_name / "scripts" / "cog" / "repo_config.json" |
| ) |
| config_path.parent.mkdir(exist_ok=True, parents=True) |
| with open(config_path, "w") as f: |
| f.write('{"fuchsia": {"repo": "fuchsia"}}') |
| |
| ws = workspace.Workspace(self.fs.repo_dir) |
| self.assertEqual(ws.config, {"fuchsia": {"repo": "fuchsia"}}) |
| |
| def test_config_file_not_found(self) -> None: |
| """Test that FileNotFoundError is raised when config file is missing.""" |
| ws = workspace.Workspace(self.fs.repo_dir) |
| with self.assertRaises(FileNotFoundError): |
| _ = ws.config |
| |
| def test_has_cartfs_dir_true(self) -> None: |
| """Test that has_cartfs_dir returns True when cartfs_dir is available.""" |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.__dict__["cartfs_dir"] = self.fs.cartfs_dir |
| self.assertTrue(ws.has_cartfs_dir) |
| |
| def test_has_cartfs_dir_false(self) -> None: |
| """Test that has_cartfs_dir returns False when cartfs_dir is not available.""" |
| ws = workspace.Workspace(self.fs.repo_dir) |
| with patch.object(ws, "_get_linked_cartfs_dir", return_value=None): |
| self.assertFalse(ws.has_cartfs_dir) |
| |
| def test_cogd_path_success(self) -> None: |
| """Test that cogd_path returns the path from git-citc.""" |
| workspace.Workspace.cogd_path.cache_clear() |
| with patch("subprocess.check_output") as mock_check_output: |
| mock_check_output.return_value = "/path/to/cogd\n" |
| path = workspace.Workspace.cogd_path() |
| self.assertEqual(path, Path("/path/to/cogd")) |
| mock_check_output.assert_called_once_with( |
| ["git-citc", "cogd"], text=True |
| ) |
| |
| def test_cogd_path_cached(self) -> None: |
| """Test that cogd_path is cached.""" |
| workspace.Workspace.cogd_path.cache_clear() |
| with patch("subprocess.check_output") as mock_check_output: |
| mock_check_output.return_value = "/path/to/cogd\n" |
| path1 = workspace.Workspace.cogd_path() |
| path2 = workspace.Workspace.cogd_path() |
| self.assertEqual(path1, path2) |
| mock_check_output.assert_called_once() |
| |
| def test_init_cogd_path_failure(self) -> None: |
| """Test that Workspace instantiation fails when cogd_path fails.""" |
| workspace.Workspace.cogd_path.cache_clear() |
| with patch("subprocess.check_output") as mock_check_output: |
| mock_check_output.side_effect = subprocess.CalledProcessError( |
| 1, "cmd" |
| ) |
| with self.assertRaises(workspace.NotInCogWorkspaceError): |
| workspace.Workspace(repo_dir=None) |
| |
| def test_init_cogd_path_not_found(self) -> None: |
| """Test that cogd_path raises RepoSetupError when git-citc is missing.""" |
| workspace.Workspace.cogd_path.cache_clear() |
| with patch("subprocess.check_output", side_effect=FileNotFoundError): |
| with self.assertRaises(workspace.NotInCogWorkspaceError): |
| workspace.Workspace.cogd_path() |
| |
| def test_init_cartfs_find_mount_point_failure(self) -> None: |
| """Test that Workspace instantiation fails when Cartfs.find_mount_point fails.""" |
| with patch.object( |
| cartfs.Cartfs, |
| "find_mount_point", |
| side_effect=cartfs.CartfsNotRunningError("mock error"), |
| ): |
| with self.assertRaises(cartfs.CartfsNotRunningError): |
| workspace.Workspace(repo_dir=self.fs.repo_dir) |
| |
| def test_init_cartfs_workspace_snapshot_success( |
| self, |
| ) -> None: |
| """Test that snapshotting from a previous instance is successful.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| suggested_directory_name = "new_cartfs_dir" |
| cartfs_instance.suggest_cartfs_dir_name.return_value = ( |
| suggested_directory_name |
| ) |
| |
| def mock_snapshot_workspace( |
| _workspace_to_snapshot_from: Path, |
| _workspace_to_snapshot_to: Path, |
| cartfs_mount_point: Path, |
| ) -> None: |
| (cartfs_mount_point / suggested_directory_name).mkdir() |
| |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| with patch.object( |
| ws, |
| "_find_previous_instance", |
| return_value=Path("foo"), |
| ), patch.object( |
| workspace.Workspace, "lock_file", new_callable=PropertyMock |
| ) as mock_lock_file, patch( |
| "workspace.snapshotter.snapshot_workspace", |
| side_effect=mock_snapshot_workspace, |
| ): |
| mock_lock_file.return_value = self.fs.cog_dir / "test.lock" |
| with ws.lock(): |
| ws.init_cartfs_workspace(snapshot=True) |
| |
| self.assertTrue( |
| self.fs.full_path( |
| suggested_directory_name, mock_fs.FSType.CARTFS |
| ).is_dir() |
| ) |
| symlink_path = self.fs.repo_dir / workspace.CARTFS_SYMLINK_NAME |
| self.assertTrue(symlink_path.is_symlink()) |
| |
| def test_init_cartfs_workspace_snapshot_no_previous_instance( |
| self, |
| ) -> None: |
| """Test that empty workspace is created when no previous instance is found.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| suggested_directory_name = "new_cartfs_dir" |
| cartfs_instance.suggest_cartfs_dir_name.return_value = ( |
| suggested_directory_name |
| ) |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| with patch.object( |
| ws, "_find_previous_instance", return_value=None |
| ), patch.object( |
| workspace.Workspace, "lock_file", new_callable=PropertyMock |
| ) as mock_lock_file: |
| mock_lock_file.return_value = self.fs.cog_dir / "test.lock" |
| with ws.lock(): |
| ws.init_cartfs_workspace(snapshot=True) |
| |
| symlink_path = self.fs.repo_dir / workspace.CARTFS_SYMLINK_NAME |
| self.assertTrue(symlink_path.is_symlink()) |
| |
| def test_init_cartfs_workspace_snapshot_snapshot_error(self) -> None: |
| """Test that empty workspace is created when snapshotting raises an error.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| suggested_directory_name = "new_cartfs_dir" |
| cartfs_instance.suggest_cartfs_dir_name.return_value = ( |
| suggested_directory_name |
| ) |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| with patch.object( |
| ws, |
| "_find_previous_instance", |
| return_value=self.fs.cartfs_dir / "previous_instance", |
| ), patch.object( |
| workspace.Workspace, "lock_file", new_callable=PropertyMock |
| ) as mock_lock_file: |
| mock_lock_file.return_value = self.fs.cog_dir / "test.lock" |
| |
| def mock_snapshot_workspace( |
| _workspace_to_snapshot_from: Path, |
| _workspace_to_snapshot_to: Path, |
| _cartfs_mount_point: Path, |
| ) -> None: |
| raise ValueError("test error") |
| |
| with ws.lock(): |
| with patch( |
| "workspace.snapshotter.snapshot_workspace", |
| side_effect=mock_snapshot_workspace, |
| ): |
| ws.init_cartfs_workspace(snapshot=True) |
| |
| symlink_path = self.fs.repo_dir / workspace.CARTFS_SYMLINK_NAME |
| self.assertTrue(symlink_path.is_symlink()) |
| |
| def test_init_cartfs_workspace_empty(self) -> None: |
| """Test that an empty cartfs workspace directory is created and linked.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| suggested_directory_name = "new_cartfs_dir" |
| cartfs_instance.suggest_cartfs_dir_name.return_value = ( |
| suggested_directory_name |
| ) |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| |
| with patch.object( |
| workspace.Workspace, "lock_file", new_callable=PropertyMock |
| ) as mock_lock_file: |
| mock_lock_file.return_value = self.fs.cog_dir / "test.lock" |
| with ws.lock(): |
| ws.init_cartfs_workspace(snapshot=False) |
| |
| expected_dir = ( |
| Path(cartfs_instance.mount_point) / suggested_directory_name |
| ) |
| self.assertTrue(expected_dir.is_dir()) |
| symlink_path = self.fs.repo_dir / workspace.CARTFS_SYMLINK_NAME |
| self.assertTrue(symlink_path.is_symlink()) |
| |
| def test_link_to_cartfs(self) -> None: |
| """Test that the workspace can be linked to a cartfs directory.""" |
| cartfs_instance = MagicMock() |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| |
| cartfs_dir = self.fs.mkdir("cartfs_dir", mock_fs.FSType.CARTFS) |
| ws._link_to_cartfs(cartfs_dir) |
| |
| symlink_path = self.fs.repo_dir / workspace.CARTFS_SYMLINK_NAME |
| self.assertTrue(symlink_path.is_symlink()) |
| |
| # Ensure that we write the name of the repository in cartfs |
| metadata = workspace.CogMetadata.from_file( |
| cartfs_dir / workspace.COG_METADATA_FILE_NAME |
| ) |
| self.assertIsNotNone(metadata) |
| |
| self.assertEqual( |
| metadata and metadata.repo_name or "", |
| "fuchsia", |
| ) |
| self.assertEqual( |
| metadata and metadata.workspace_name or "", |
| "test-workspace", |
| ) |
| self.assertEqual( |
| metadata and metadata.workspace_id or "", |
| self.fs.workspace_id, |
| ) |
| |
| def test_find_previous_instance_success(self) -> None: |
| """Test that the previous instance is found correctly.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| |
| # Create a candidate directory. |
| candidate_dir = self.fs.mkdir("candidate", mock_fs.FSType.CARTFS) |
| workspace.CogMetadata( |
| workspace_name="test-workspace", |
| repo_name="fuchsia", |
| ).write(candidate_dir) |
| |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| |
| result = ws._find_previous_instance() |
| self.assertEqual(result, Path("candidate")) |
| |
| def test_find_previous_instance_ignores_current_workspace(self) -> None: |
| """Test that the current workspace is ignored.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| |
| result = ws._find_previous_instance() |
| self.assertIsNone(result) |
| |
| def test_find_previous_instance_ignores_other_repo(self) -> None: |
| """Test that the previous instance is skipped if it is for a different repository.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| |
| # Create a candidate directory. |
| candidate_dir = self.fs.mkdir("candidate", mock_fs.FSType.CARTFS) |
| workspace.CogMetadata( |
| workspace_name="test-workspace", |
| repo_name="other-repo", |
| ).write(candidate_dir) |
| |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| |
| result = ws._find_previous_instance() |
| self.assertIsNone(result) |
| |
| def test_find_previous_instance_no_candidates(self) -> None: |
| """Test that None is returned when there are no candidate directories.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| |
| result = ws._find_previous_instance() |
| self.assertIsNone(result) |
| |
| def test_golden_snapshot_dir_fuchsia_repo(self) -> None: |
| """Test that _golden_snapshot_dir returns None for fuchsia repo.""" |
| ws = workspace.Workspace(self.fs.repo_dir) |
| self.assertIsNone(ws._golden_snapshot_dir) |
| |
| def test_golden_snapshot_dir_internal_superproject_missing(self) -> None: |
| """Test that _golden_snapshot_dir returns None if .fuchsia_golden_snapshot missing.""" |
| ws = workspace.Workspace( |
| self.fs.repo_dir.parent / "fuchsia-cog-superproject" |
| ) |
| self.assertIsNone(ws._golden_snapshot_dir) |
| |
| def test_golden_snapshot_dir_outside_mount_point(self) -> None: |
| """Test that _golden_snapshot_dir returns None if resolved path is outside cartfs mount point.""" |
| ws = workspace.Workspace( |
| self.fs.repo_dir.parent / "fuchsia-cog-superproject" |
| ) |
| outside_dir = self.fs.cog_dir / "outside_golden" |
| outside_dir.mkdir(parents=True, exist_ok=True) |
| (outside_dir / ".integration_commit_hash").touch() |
| |
| golden_symlink = self.fs.cartfs_dir / ".fuchsia_golden_snapshot" |
| golden_symlink.symlink_to(outside_dir) |
| |
| self.assertIsNone(ws._golden_snapshot_dir) |
| |
| def test_golden_snapshot_dir_missing_commit_hash(self) -> None: |
| """Test that _golden_snapshot_dir returns None if .integration_commit_hash is missing.""" |
| ws = workspace.Workspace( |
| self.fs.repo_dir.parent / "fuchsia-cog-superproject" |
| ) |
| inside_dir = self.fs.cartfs_dir / "golden_snapshot_v1" |
| inside_dir.mkdir(parents=True, exist_ok=True) |
| |
| golden_symlink = self.fs.cartfs_dir / ".fuchsia_golden_snapshot" |
| golden_symlink.symlink_to(inside_dir) |
| |
| self.assertIsNone(ws._golden_snapshot_dir) |
| |
| def test_golden_snapshot_dir_valid(self) -> None: |
| """Test that _golden_snapshot_dir returns relative path for valid golden snapshot.""" |
| ws = workspace.Workspace( |
| self.fs.repo_dir.parent / "fuchsia-cog-superproject" |
| ) |
| inside_dir = self.fs.cartfs_dir / "golden_snapshot_v1" |
| inside_dir.mkdir(parents=True, exist_ok=True) |
| (inside_dir / ".integration_commit_hash").touch() |
| |
| golden_symlink = self.fs.cartfs_dir / ".fuchsia_golden_snapshot" |
| golden_symlink.symlink_to(inside_dir) |
| |
| self.assertEqual(ws._golden_snapshot_dir, Path("golden_snapshot_v1")) |
| |
| def test_is_cartfs_workspace_clean_initially_false(self) -> None: |
| """Test that _is_cartfs_workspace_clean is initially False.""" |
| ws = workspace.Workspace(self.fs.repo_dir) |
| self.assertFalse(ws._is_cartfs_workspace_clean) |
| |
| def test_is_cartfs_workspace_clean_after_empty_init(self) -> None: |
| """Test that _is_cartfs_workspace_clean is True after initializing an empty workspace.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| suggested_directory_name = "new_cartfs_dir" |
| cartfs_instance.suggest_cartfs_dir_name.return_value = ( |
| suggested_directory_name |
| ) |
| |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| |
| with patch.object( |
| workspace.Workspace, "lock_file", new_callable=PropertyMock |
| ) as mock_lock_file: |
| mock_lock_file.return_value = self.fs.cog_dir / "test.lock" |
| with ws.lock(): |
| ws.init_cartfs_workspace(snapshot=False) |
| |
| self.assertTrue(ws._is_cartfs_workspace_clean) |
| |
| def test_is_cartfs_workspace_clean_after_golden_snapshot_init(self) -> None: |
| """Test that _is_cartfs_workspace_clean is True after initializing from golden snapshot.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| suggested_directory_name = "new_cartfs_dir" |
| cartfs_instance.suggest_cartfs_dir_name.return_value = ( |
| suggested_directory_name |
| ) |
| |
| def mock_snapshot_workspace( |
| _workspace_to_snapshot_from: Path, |
| _workspace_to_snapshot_to: Path, |
| cartfs_mount_point: Path, |
| ) -> None: |
| (cartfs_mount_point / suggested_directory_name).mkdir() |
| |
| ws_repo_dir = self.fs.repo_dir.parent / "fuchsia-cog-superproject" |
| ws_repo_dir.mkdir(parents=True, exist_ok=True) |
| ws = workspace.Workspace(ws_repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| |
| with ( |
| patch.object( |
| workspace.Workspace, |
| "_golden_snapshot_dir", |
| new_callable=PropertyMock, |
| return_value=Path("golden_snapshot_v1"), |
| ), |
| patch.object( |
| workspace.Workspace, "lock_file", new_callable=PropertyMock |
| ) as mock_lock_file, |
| patch( |
| "workspace.snapshotter.snapshot_workspace", |
| side_effect=mock_snapshot_workspace, |
| ), |
| patch.object(ws, "_sync_cog_to_golden_revision") as mock_sync, |
| ): |
| mock_lock_file.return_value = self.fs.cog_dir / "test.lock" |
| with ws.lock(): |
| ws.init_cartfs_workspace(snapshot=True) |
| |
| self.assertTrue(ws._is_cartfs_workspace_clean) |
| mock_sync.assert_called_once() |
| |
| def test_is_cartfs_workspace_clean_after_previous_snapshot_init( |
| self, |
| ) -> None: |
| """Test that _is_cartfs_workspace_clean is False after initializing from a previous instance.""" |
| cartfs_instance = MagicMock() |
| cartfs_instance.mount_point = self.fs.cartfs_dir |
| suggested_directory_name = "new_cartfs_dir" |
| cartfs_instance.suggest_cartfs_dir_name.return_value = ( |
| suggested_directory_name |
| ) |
| |
| def mock_snapshot_workspace( |
| _workspace_to_snapshot_from: Path, |
| _workspace_to_snapshot_to: Path, |
| cartfs_mount_point: Path, |
| ) -> None: |
| (cartfs_mount_point / suggested_directory_name).mkdir() |
| |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws.cartfs_instance = cartfs_instance |
| |
| with ( |
| patch.object( |
| workspace.Workspace, |
| "_golden_snapshot_dir", |
| new_callable=PropertyMock, |
| return_value=None, |
| ), |
| patch.object( |
| ws, |
| "_find_previous_instance", |
| return_value=Path("previous_instance"), |
| ), |
| patch.object( |
| workspace.Workspace, "lock_file", new_callable=PropertyMock |
| ) as mock_lock_file, |
| patch( |
| "workspace.snapshotter.snapshot_workspace", |
| side_effect=mock_snapshot_workspace, |
| ), |
| patch.object(ws, "_sync_cog_to_golden_revision") as mock_sync, |
| ): |
| mock_lock_file.return_value = self.fs.cog_dir / "test.lock" |
| with ws.lock(): |
| ws.init_cartfs_workspace(snapshot=True) |
| |
| self.assertFalse(ws._is_cartfs_workspace_clean) |
| mock_sync.assert_not_called() |
| |
| def test_write_jiri_config_when_clean(self) -> None: |
| """Test that _write_jiri_config does not clean/restore git when _is_cartfs_workspace_clean is True.""" |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws._is_cartfs_workspace_clean = True |
| ws.__dict__["cartfs_dir"] = self.fs.cartfs_dir |
| |
| with ( |
| patch.object(ws, "_create_symlink") as mock_symlink, |
| patch.object(ws, "_run") as mock_run, |
| ): |
| ws._write_jiri_config() |
| |
| # Ensure we ran jiri init but not clean/restore |
| mock_run.assert_called_once_with( |
| [".jiri_root/bin/jiri", "init", "-analytics-opt=true"], |
| cwd=ws.cartfs_fuchsia_dir, |
| ) |
| |
| def test_write_jiri_config_when_dirty(self) -> None: |
| """Test that _write_jiri_config cleans and restores git when _is_cartfs_workspace_clean is False.""" |
| ws = workspace.Workspace(self.fs.repo_dir) |
| ws._is_cartfs_workspace_clean = False |
| ws.__dict__["cartfs_dir"] = self.fs.cartfs_dir |
| |
| with ( |
| patch.object(ws, "_create_symlink") as mock_symlink, |
| patch.object(ws, "_run") as mock_run, |
| ): |
| ws._write_jiri_config() |
| |
| self.assertEqual(mock_run.call_count, 3) |
| mock_run.assert_any_call( |
| [".jiri_root/bin/jiri", "init", "-analytics-opt=true"], |
| cwd=ws.cartfs_fuchsia_dir, |
| ) |
| mock_run.assert_any_call( |
| [".jiri_root/bin/jiri", "runp", "git", "clean", "-df"], |
| cwd=ws.cartfs_fuchsia_dir, |
| ) |
| mock_run.assert_any_call( |
| [".jiri_root/bin/jiri", "runp", "git", "restore", "."], |
| cwd=ws.cartfs_fuchsia_dir, |
| ) |
| |
| def test_checkout_cartfs_to_cog_revisions_up_to_date_no_cog_integration( |
| self, |
| ) -> None: |
| """Test that initialization skips sync and symlinks when up-to-date.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| ws.__dict__["config"] = { |
| "integration_url": "https://fuchsia.googlesource.com/integration", |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": None, |
| }, |
| "jiriImports": [], |
| } |
| |
| with ( |
| patch.object(ws, "get_cog_commit", return_value="hash123"), |
| patch.object(ws, "get_cartfs_commit", return_value="hash123"), |
| patch.object(ws, "_sync_fuchsia_repo") as mock_sync, |
| patch.object(ws, "_create_symlinks") as mock_symlinks, |
| patch.object(ws, "_fetch_prebuilts") as mock_fetch, |
| patch.object(ws, "_reinit_integration_repo") as mock_reinit, |
| patch.object( |
| ws, "_checkout_integration_roll", return_value="int_hash" |
| ) as mock_checkout, |
| patch.object(ws, "_is_jiri_bootstrapped", return_value=True), |
| patch("subprocess.run"), |
| ): |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| ws.checkout_cartfs_to_cog_revisions() |
| mock_assert_locked.assert_called_once() |
| |
| mock_sync.assert_called_once_with("hash123") |
| mock_symlinks.assert_called_once() |
| |
| def test_checkout_cartfs_to_cog_revisions_up_to_date_with_cog_integration( |
| self, |
| ) -> None: |
| """Test that initialization skips sync and symlinks when up-to-date (including integration).""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| ws.__dict__["config"] = { |
| "integration_url": "https://fuchsia.googlesource.com/integration", |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": "integration", |
| }, |
| "jiriImports": [], |
| } |
| |
| def mock_get_cog_commit(repo: str) -> str: |
| return "fuchsia_hash" if repo == "fuchsia" else "int_hash" |
| |
| def mock_get_cartfs_commit(repo: str) -> str: |
| return "fuchsia_hash" if repo == "fuchsia" else "int_hash" |
| |
| with ( |
| patch.object( |
| ws, "get_cog_commit", side_effect=mock_get_cog_commit |
| ), |
| patch.object( |
| ws, "get_cartfs_commit", side_effect=mock_get_cartfs_commit |
| ), |
| patch.object(ws, "_sync_fuchsia_repo") as mock_sync, |
| patch.object(ws, "_create_symlinks") as mock_symlinks, |
| patch.object(ws, "_fetch_prebuilts") as mock_fetch, |
| patch.object(ws, "_reinit_integration_repo") as mock_reinit, |
| patch.object(ws, "_is_jiri_bootstrapped", return_value=True), |
| patch("subprocess.run"), |
| ): |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| ws.checkout_cartfs_to_cog_revisions() |
| mock_assert_locked.assert_called_once() |
| |
| mock_sync.assert_called_once_with("fuchsia_hash") |
| mock_symlinks.assert_called_once() |
| |
| def test_checkout_cartfs_to_cog_revisions_not_up_to_date_no_cog_integration( |
| self, |
| ) -> None: |
| """Test that initialization performs sync when not up-to-date (no integration repo).""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| ws.__dict__["config"] = { |
| "integration_url": "https://fuchsia.googlesource.com/integration", |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": None, |
| }, |
| "jiriImports": [], |
| "symlinks": {}, |
| } |
| |
| with ( |
| patch.object(ws, "get_cog_commit", return_value="hash123"), |
| patch.object( |
| ws, "get_cartfs_commit", return_value="different-hash" |
| ), |
| patch.object(ws, "_sync_fuchsia_repo") as mock_sync, |
| patch.object(ws, "_create_symlinks") as mock_symlinks, |
| patch.object(ws, "_fetch_prebuilts") as mock_fetch, |
| patch.object(ws, "_reinit_integration_repo") as mock_reinit, |
| patch.object( |
| ws, |
| "_checkout_integration_roll", |
| return_value="integration_hash_abc", |
| ) as mock_checkout, |
| patch.object(ws, "_is_jiri_bootstrapped", return_value=True), |
| patch("subprocess.run"), |
| ): |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| ws.checkout_cartfs_to_cog_revisions() |
| mock_assert_locked.assert_called_once() |
| |
| mock_reinit.assert_called_once() |
| mock_checkout.assert_called_once_with("hash123") |
| mock_sync.assert_called_once_with("hash123") |
| mock_symlinks.assert_called_once() |
| mock_fetch.assert_called_once() |
| |
| def test_checkout_cartfs_to_cog_revisions_not_up_to_date_with_cog_integration( |
| self, |
| ) -> None: |
| """Test that initialization performs sync when not up-to-date (with integration repo).""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| ws.__dict__["config"] = { |
| "integration_url": "https://fuchsia.googlesource.com/integration", |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": "integration", |
| }, |
| "jiriImports": [], |
| "symlinks": {}, |
| } |
| |
| def mock_get_cog_commit(repo: str) -> str: |
| return "fuchsia_hash" if repo == "fuchsia" else "int_hash" |
| |
| def mock_get_cartfs_commit(repo: str) -> str: |
| return ( |
| "diff_fuchsia_hash" |
| if repo == "fuchsia" |
| else "diff_int_hash" |
| ) |
| |
| with ( |
| patch.object( |
| ws, "get_cog_commit", side_effect=mock_get_cog_commit |
| ), |
| patch.object( |
| ws, "get_cartfs_commit", side_effect=mock_get_cartfs_commit |
| ), |
| patch.object(ws, "_sync_fuchsia_repo") as mock_sync, |
| patch.object(ws, "_create_symlinks") as mock_symlinks, |
| patch.object(ws, "_fetch_prebuilts") as mock_fetch, |
| patch.object(ws, "_reinit_integration_repo") as mock_reinit, |
| patch.object(ws, "_checkout_integration_roll") as mock_checkout, |
| patch.object(ws, "_is_jiri_bootstrapped", return_value=True), |
| patch("subprocess.run"), |
| ): |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| ws.checkout_cartfs_to_cog_revisions() |
| mock_assert_locked.assert_called_once() |
| |
| mock_reinit.assert_called_once_with("int_hash") |
| mock_checkout.assert_not_called() |
| mock_sync.assert_called_once_with("fuchsia_hash") |
| mock_symlinks.assert_called_once() |
| mock_fetch.assert_called_once() |
| |
| def test_create_symlinks_resolves_paths(self) -> None: |
| """Test that _create_symlinks correctly resolves @cog// and @cartfs// paths.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| |
| assert ws.cartfs_dir is not None |
| |
| mock_config_patcher = patch.object( |
| workspace.Workspace, "config", new_callable=PropertyMock |
| ) |
| mock_config = mock_config_patcher.start() |
| mock_config.return_value = { |
| "integration_url": "https://fuchsia.googlesource.com/integration", |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": None, |
| }, |
| "symlinkMap": { |
| "@cog//dest_path": "@cartfs//src_path", |
| }, |
| } |
| self.addCleanup(mock_config_patcher.stop) |
| |
| with ( |
| patch.object(ws, "_create_symlink") as mock_create_symlink, |
| patch.object(ws, "_run") as mock_run, |
| patch("subprocess.Popen") as mock_popen, |
| ): |
| # Mock directory creation to avoid side effects |
| with patch.object(Path, "mkdir"): |
| ws._create_symlinks() |
| |
| mock_create_symlink.assert_called_once_with( |
| fs.cartfs_dir / "src_path", |
| fs.full_path("test-workspace", mock_fs.FSType.COG) |
| / "fuchsia" |
| / "dest_path", |
| ) |
| |
| # Test invalid root raises KeyError |
| mock_config.return_value = { |
| "integration_url": "https://fuchsia.googlesource.com/integration", |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": None, |
| }, |
| "symlinkMap": { |
| "@cog//dest_path": "@invalid//src_path", |
| }, |
| } |
| with self.assertRaises(KeyError): |
| ws._create_symlinks() |
| |
| def test_write_jiri_manifest_uses_imports(self) -> None: |
| """Test that _write_jiri_manifest uses jiriImports from config.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| |
| mock_config_patcher = patch.object( |
| workspace.Workspace, "config", new_callable=PropertyMock |
| ) |
| mock_config = mock_config_patcher.start() |
| mock_config.return_value = { |
| "integration_url": "https://fuchsia.googlesource.com/integration", |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": None, |
| }, |
| "jiriImports": [ |
| "manifest/path1", |
| "manifest/path2", |
| ], |
| } |
| self.addCleanup(mock_config_patcher.stop) |
| |
| ws._write_jiri_manifest() |
| |
| manifest_path = fs.cartfs_dir / "fuchsia" / ".jiri_manifest" |
| self.assertTrue(manifest_path.exists()) |
| |
| content = manifest_path.read_text() |
| self.assertIn('<localimport file="manifest/path1"/>', content) |
| self.assertIn('<localimport file="manifest/path2"/>', content) |
| |
| def test_checkout_cartfs_to_cog_revisions_with_custom_integration( |
| self, |
| ) -> None: |
| """Test that specifying integration.repo and integration.remote changes behavior.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| |
| mock_config_patcher = patch.object( |
| workspace.Workspace, "config", new_callable=PropertyMock |
| ) |
| mock_config = mock_config_patcher.start() |
| mock_config.return_value = { |
| "integration_url": "https://custom.git/integration", |
| "repo": { |
| "fuchsia": "custom-fuchsia", |
| "integration": "custom-integration", |
| }, |
| "jiriImports": [], |
| } |
| self.addCleanup(mock_config_patcher.stop) |
| |
| with ( |
| patch.object(ws, "get_cog_commit") as mock_get_cog_commit, |
| patch.object(ws, "get_cartfs_commit", return_value="hash123"), |
| patch.object(ws, "_sync_fuchsia_repo") as mock_sync, |
| patch.object(ws, "_create_symlinks") as mock_symlinks, |
| patch.object(ws, "_fetch_prebuilts") as mock_fetch, |
| patch.object( |
| ws, "_checkout_integration_roll" |
| ) as mock_checkout_roll, |
| patch.object(ws, "_is_jiri_bootstrapped", return_value=True), |
| patch.object(ws, "_write_jiri_config"), |
| patch("subprocess.run") as mock_subprocess_run, |
| ): |
| mock_get_cog_commit.side_effect = lambda repo: { |
| "custom-fuchsia": "fuchsia_hash", |
| "custom-integration": "integration_hash", |
| }[repo] |
| |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| ws.checkout_cartfs_to_cog_revisions() |
| mock_assert_locked.assert_called_once() |
| |
| # Should bypass checkout_integration_roll |
| mock_checkout_roll.assert_not_called() |
| |
| # Should call sync_fuchsia_repo |
| mock_sync.assert_called_once_with("fuchsia_hash") |
| |
| # Should call subprocess.run (for git clone) in _reinit_integration_repo |
| mock_subprocess_run.assert_called_once() |
| args, kwargs = mock_subprocess_run.call_args |
| cmd = args[0] |
| self.assertIn("https://custom.git/integration", cmd) |
| self.assertIn("--revision=integration_hash", cmd) |
| |
| # Should call _fetch_prebuilts |
| mock_fetch.assert_called_once() |
| |
| def test_is_checkout_uptodate_standalone_true(self) -> None: |
| """Test that returns True when standalone checkout is up to date.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| ws.__dict__["config"] = { |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": None, |
| }, |
| } |
| with ( |
| patch.object(ws, "get_cog_commit", return_value="hash123"), |
| patch.object(ws, "get_cartfs_commit", return_value="hash123"), |
| ): |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| self.assertTrue(ws.is_checkout_uptodate()) |
| mock_assert_locked.assert_called_once() |
| |
| def test_is_checkout_uptodate_standalone_false(self) -> None: |
| """Test that returns False when standalone checkout is not up to date.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| ws.__dict__["config"] = { |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": None, |
| }, |
| } |
| with ( |
| patch.object(ws, "get_cog_commit", return_value="hash123"), |
| patch.object( |
| ws, "get_cartfs_commit", return_value="different-hash" |
| ), |
| ): |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| self.assertFalse(ws.is_checkout_uptodate()) |
| mock_assert_locked.assert_called_once() |
| |
| def test_is_checkout_uptodate_superproject_true(self) -> None: |
| """Test that returns True when superproject checkout is up to date.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| ws.__dict__["config"] = { |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": "integration", |
| }, |
| "useJiriUpdateOnly": True, |
| } |
| |
| def mock_get_commit(repo: str) -> str: |
| return "fuchsia_hash" if repo == "fuchsia" else "int_hash" |
| |
| with ( |
| patch.object(ws, "get_cog_commit", side_effect=mock_get_commit), |
| patch.object( |
| ws, "get_cartfs_commit", side_effect=mock_get_commit |
| ), |
| ): |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| self.assertTrue(ws.is_checkout_uptodate()) |
| mock_assert_locked.assert_called_once() |
| |
| def test_is_checkout_uptodate_superproject_false_fuchsia_differs( |
| self, |
| ) -> None: |
| """Test that returns False when fuchsia commit differs in superproject.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| ws.__dict__["config"] = { |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": "integration", |
| }, |
| "useJiriUpdateOnly": True, |
| } |
| with ( |
| patch.object(ws, "get_cog_commit", return_value="fuchsia_hash"), |
| patch.object(ws, "get_cartfs_commit", return_value="diff_hash"), |
| ): |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| self.assertFalse(ws.is_checkout_uptodate()) |
| mock_assert_locked.assert_called_once() |
| |
| def test_is_checkout_uptodate_superproject_false_integration_differs( |
| self, |
| ) -> None: |
| """Test that returns False when integration commit differs in superproject.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace( |
| fs.full_path("test-workspace", mock_fs.FSType.COG) / "fuchsia", |
| ) |
| ws.__dict__["cartfs_dir"] = fs.cartfs_dir |
| ws.__dict__["config"] = { |
| "repo": { |
| "fuchsia": "fuchsia", |
| "integration": "integration", |
| }, |
| "useJiriUpdateOnly": True, |
| } |
| |
| def mock_get_cog_commit(repo: str) -> str: |
| return "fuchsia_hash" if repo == "fuchsia" else "int_hash" |
| |
| def mock_get_cartfs_commit(repo: str) -> str: |
| return "fuchsia_hash" if repo == "fuchsia" else "diff_int_hash" |
| |
| with ( |
| patch.object( |
| ws, "get_cog_commit", side_effect=mock_get_cog_commit |
| ), |
| patch.object( |
| ws, "get_cartfs_commit", side_effect=mock_get_cartfs_commit |
| ), |
| ): |
| with patch.object(ws, "_assert_locked") as mock_assert_locked: |
| self.assertFalse(ws.is_checkout_uptodate()) |
| mock_assert_locked.assert_called_once() |
| |
| def test_lock_acquisition(self) -> None: |
| """Test that lock can be acquired.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace(fs.repo_dir) |
| with patch.object( |
| workspace.Workspace, "lock_file", new_callable=PropertyMock |
| ) as mock_lock_file: |
| mock_lock_file.return_value = fs.cog_dir / "test.lock" |
| |
| self.assertEqual(ws._lock_count, 0) |
| with ws.lock(): |
| self.assertEqual(ws._lock_count, 1) |
| self.assertTrue((fs.cog_dir / "test.lock").exists()) |
| lock_content = (fs.cog_dir / "test.lock").read_text() |
| self.assertEqual(lock_content, str(os.getpid())) |
| self.assertEqual(ws._lock_count, 0) |
| |
| def test_lock_reentrant(self) -> None: |
| """Test that lock is reentrant.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace(fs.repo_dir) |
| with patch.object( |
| workspace.Workspace, "lock_file", new_callable=PropertyMock |
| ) as mock_lock_file: |
| mock_lock_file.return_value = fs.cog_dir / "test.lock" |
| |
| with ws.lock(): |
| self.assertEqual(ws._lock_count, 1) |
| with ws.lock(): |
| self.assertEqual(ws._lock_count, 2) |
| self.assertEqual(ws._lock_count, 1) |
| self.assertEqual(ws._lock_count, 0) |
| |
| def test_lock_blocks_and_resumes(self) -> None: |
| """Test that a second workspace instance blocks and resumes.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws1 = workspace.Workspace(fs.repo_dir) |
| ws2 = workspace.Workspace(fs.repo_dir) |
| |
| # Mock Path.home() to return fs.cog_dir |
| with patch.object(Path, "home", return_value=fs.cog_dir): |
| lock_acquired_by_ws1 = threading.Event() |
| ws2_can_proceed = threading.Event() |
| ws2_finished = threading.Event() |
| |
| def ws2_worker() -> None: |
| # Wait for ws1 to hold the lock |
| lock_acquired_by_ws1.wait() |
| # This should block until ws1 releases the lock |
| with ws2.lock(): |
| ws2_can_proceed.set() |
| ws2_finished.set() |
| |
| t = threading.Thread(target=ws2_worker) |
| t.start() |
| |
| with ws1.lock(): |
| lock_acquired_by_ws1.set() |
| # ws2 should be blocked now. |
| # We assert that ws2 has NOT proceeded yet. |
| self.assertFalse(ws2_can_proceed.is_set()) |
| # Give worker thread a moment to try and block |
| time.sleep(0.5) |
| self.assertFalse(ws2_can_proceed.is_set()) |
| |
| # After ws1 exits lock, ws2 should proceed. |
| ws2_finished.wait(timeout=5) |
| self.assertTrue(ws2_can_proceed.is_set()) |
| t.join() |
| |
| def test_assert_locked_raises(self) -> None: |
| """Test that _assert_locked raises error when not locked.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace(fs.repo_dir) |
| with self.assertRaises(workspace.WorkspaceError): |
| ws._assert_locked() |
| |
| def test_assert_locked_passes(self) -> None: |
| """Test that _assert_locked passes when locked.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace(fs.repo_dir) |
| ws._lock_count = 1 |
| ws._assert_locked() |
| |
| def test_lock_decorator(self) -> None: |
| """Test that the lock decorator acquires the lock.""" |
| mock_ws = MagicMock() |
| |
| class SomeClass: |
| def __init__(self) -> None: |
| self.workspace: workspace.Workspace = mock_ws |
| |
| @workspace.lock |
| def decorated_method(self, x: int) -> int: |
| return x * 2 |
| |
| some_object = SomeClass() |
| mock_ws.lock.assert_not_called() |
| |
| result = some_object.decorated_method(21) |
| self.assertEqual(result, 42) |
| mock_ws.lock.assert_called_once() |
| |
| def test_sync_cog_to_golden_revision_success(self) -> None: |
| """Test that _sync_cog_to_golden_revision successfully rebases the superproject.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace(fs.repo_dir) |
| ws.cartfs_dir = fs.cartfs_dir |
| |
| # Setup fake superproject directory in CartFS |
| superproject_dir = fs.cartfs_dir / "fuchsia-cog-superproject" |
| superproject_dir.mkdir(parents=True, exist_ok=True) |
| |
| target_commit = "ca69b3f0bfe3b37612bbf2e95fbd29ec8b31012d" |
| |
| def mock_run( |
| cmd: list[str], cwd: Path, capture_output: bool = False |
| ) -> str: |
| if ( |
| cmd == ["git", "rev-parse", "HEAD"] |
| and cwd == superproject_dir |
| ): |
| return target_commit |
| return "" |
| |
| with ( |
| patch.object(ws, "_run", side_effect=mock_run) as mock_run_spy, |
| patch.object(workspace.logger, "emit_status") as mock_emit, |
| patch.object(workspace.logger, "log_info") as mock_log_info, |
| ): |
| ws._sync_cog_to_golden_revision() |
| |
| # Verify git rev-parse HEAD was called |
| mock_run_spy.assert_any_call( |
| ["git", "rev-parse", "HEAD"], |
| cwd=superproject_dir, |
| capture_output=True, |
| ) |
| |
| # Verify git-citc api.call Rebase was called |
| mock_run_spy.assert_any_call( |
| [ |
| "git-citc", |
| "api.call", |
| "Rebase", |
| f'repo_root: "{ws.repo_name}" new_base: "{target_commit}"', |
| ], |
| cwd=ws.repo_dir, |
| capture_output=True, |
| ) |
| |
| def test_sync_cog_to_golden_revision_missing_dir(self) -> None: |
| """Test that _sync_cog_to_golden_revision logs error if directory missing.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace(fs.repo_dir) |
| ws.cartfs_dir = fs.cartfs_dir |
| # Do NOT create the directory |
| |
| with ( |
| patch.object(ws, "_run") as mock_run, |
| patch.object(workspace.logger, "log_error") as mock_log_error, |
| ): |
| ws._sync_cog_to_golden_revision() |
| |
| # Verify it logged the error |
| mock_log_error.assert_called_once() |
| self.assertIn( |
| "directory not found in CartFS", |
| mock_log_error.call_args[0][0], |
| ) |
| |
| # Verify no run calls were made |
| mock_run.assert_not_called() |
| |
| def test_sync_cog_to_golden_revision_git_failure(self) -> None: |
| """Test that _sync_cog_to_golden_revision handles git failure gracefully.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace(fs.repo_dir) |
| ws.cartfs_dir = fs.cartfs_dir |
| |
| # Setup fake superproject directory in CartFS |
| superproject_dir = fs.cartfs_dir / "fuchsia-cog-superproject" |
| superproject_dir.mkdir(parents=True, exist_ok=True) |
| |
| def mock_run( |
| cmd: list[str], cwd: Path, capture_output: bool = False |
| ) -> str: |
| if ( |
| cmd == ["git", "rev-parse", "HEAD"] |
| and cwd == superproject_dir |
| ): |
| raise subprocess.CalledProcessError( |
| 128, cmd, stderr=b"fatal: not a git repository" |
| ) |
| return "" |
| |
| with ( |
| patch.object(ws, "_run", side_effect=mock_run), |
| patch.object(workspace.logger, "log_error") as mock_log_error, |
| ): |
| ws._sync_cog_to_golden_revision() |
| |
| # Verify error is logged and includes the stderr of the process |
| mock_log_error.assert_called_once() |
| self.assertIn( |
| "Failed to get superproject commit from CartFS snapshot", |
| mock_log_error.call_args[0][0], |
| ) |
| self.assertIn( |
| "fatal: not a git repository", |
| mock_log_error.call_args[0][0], |
| ) |
| |
| def test_sync_cog_to_golden_revision_rebase_failure(self) -> None: |
| """Test that _sync_cog_to_golden_revision handles git-citc rebase failure gracefully.""" |
| with mock_fs.FileSystemTestHelper() as fs: |
| ws = workspace.Workspace(fs.repo_dir) |
| ws.cartfs_dir = fs.cartfs_dir |
| |
| # Setup fake superproject directory in CartFS |
| superproject_dir = fs.cartfs_dir / "fuchsia-cog-superproject" |
| superproject_dir.mkdir(parents=True, exist_ok=True) |
| |
| target_commit = "ca69b3f0bfe3b37612bbf2e95fbd29ec8b31012d" |
| |
| def mock_run( |
| cmd: list[str], cwd: Path, capture_output: bool = False |
| ) -> str: |
| if ( |
| cmd == ["git", "rev-parse", "HEAD"] |
| and cwd == superproject_dir |
| ): |
| return target_commit |
| if "git-citc" in cmd: |
| raise subprocess.CalledProcessError( |
| 1, cmd, stderr=b"Rebase failed because of conflict" |
| ) |
| return "" |
| |
| with ( |
| patch.object(ws, "_run", side_effect=mock_run), |
| patch.object(workspace.logger, "log_error") as mock_log_error, |
| ): |
| ws._sync_cog_to_golden_revision() |
| |
| # Verify error is logged and includes the stderr of the process |
| mock_log_error.assert_called_once() |
| self.assertIn( |
| "Failed to sync Cog CITC base", |
| mock_log_error.call_args[0][0], |
| ) |
| self.assertIn( |
| "Rebase failed because of conflict", |
| mock_log_error.call_args[0][0], |
| ) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |