summaryrefslogtreecommitdiff
path: root/tests/test_subcmds_sync.py
diff options
context:
space:
mode:
authorGavin Mak <gavinmak@google.com>2025-06-13 17:53:38 -0700
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2025-06-18 10:26:27 -0700
commit7b6ffed4ae3102b7c90592eeff8e28855cc25c11 (patch)
tree13be6b1470be5f3a2fad04797813936c034bb024 /tests/test_subcmds_sync.py
parentb4b323a8bd02d52d060f7f6fa15ba045df5af5b2 (diff)
downloadgit-repo-7b6ffed4ae3102b7c90592eeff8e28855cc25c11.tar.gz
git-repo-7b6ffed4ae3102b7c90592eeff8e28855cc25c11.zip
sync: Implement --interleaved sync worker
For each assigned project, the worker sequentially calls Sync_NetworkHalf and Sync_LocalHalf, respecting --local-only and --network-only flags. To prevent scrambled progress bars, all stderr output from the checkout phase is captured (shown with --verbose). Result objects now carry status and timing information from the worker for state updates. Bug: 421935613 Change-Id: I398602e08a375e974a8914e5fa48ffae673dda9b Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/483301 Commit-Queue: Gavin Mak <gavinmak@google.com> Reviewed-by: Scott Lee <ddoman@google.com> Tested-by: Gavin Mak <gavinmak@google.com>
Diffstat (limited to 'tests/test_subcmds_sync.py')
-rw-r--r--tests/test_subcmds_sync.py181
1 files changed, 180 insertions, 1 deletions
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py
index 60f283afe..e7213ed90 100644
--- a/tests/test_subcmds_sync.py
+++ b/tests/test_subcmds_sync.py
@@ -310,6 +310,16 @@ class FakeProject:
self.name = name or relpath
self.objdir = objdir or relpath
+ self.use_git_worktrees = False
+ self.UseAlternates = False
+ self.manifest = mock.MagicMock()
+ self.manifest.GetProjectsWithName.return_value = [self]
+ self.config = mock.MagicMock()
+ self.EnableRepositoryExtension = mock.MagicMock()
+
+ def RelPath(self, local=None):
+ return self.relpath
+
def __str__(self):
return f"project: {self.relpath}"
@@ -531,7 +541,11 @@ class InterleavedSyncTest(unittest.TestCase):
self.manifest.CloneBundle = False
self.manifest.default.sync_j = 1
- self.cmd = sync.Sync(manifest=self.manifest)
+ self.outer_client = mock.MagicMock()
+ self.outer_client.manifest.IsArchive = False
+ self.cmd = sync.Sync(
+ manifest=self.manifest, outer_client=self.outer_client
+ )
self.cmd.outer_manifest = self.manifest
# Mock projects.
@@ -549,6 +563,21 @@ class InterleavedSyncTest(unittest.TestCase):
mock.patch.object(sync, "_PostRepoUpgrade").start()
mock.patch.object(sync, "_PostRepoFetch").start()
+ # Mock parallel context for worker tests.
+ self.parallel_context_patcher = mock.patch(
+ "subcmds.sync.Sync.get_parallel_context"
+ )
+ self.mock_get_parallel_context = self.parallel_context_patcher.start()
+ self.sync_dict = {}
+ self.mock_context = {
+ "projects": [],
+ "sync_dict": self.sync_dict,
+ }
+ self.mock_get_parallel_context.return_value = self.mock_context
+
+ # Mock _GetCurrentBranchOnly for worker tests.
+ mock.patch.object(sync.Sync, "_GetCurrentBranchOnly").start()
+
def tearDown(self):
"""Clean up resources."""
shutil.rmtree(self.repodir)
@@ -635,3 +664,153 @@ class InterleavedSyncTest(unittest.TestCase):
work_items_sets = {frozenset(item) for item in work_items}
expected_sets = {frozenset([0, 2]), frozenset([1])}
self.assertEqual(work_items_sets, expected_sets)
+
+ def _get_opts(self, args=None):
+ """Helper to get default options for worker tests."""
+ if args is None:
+ args = ["--interleaved"]
+ opt, _ = self.cmd.OptionParser.parse_args(args)
+ # Set defaults for options used by the worker.
+ opt.quiet = True
+ opt.verbose = False
+ opt.force_sync = False
+ opt.clone_bundle = False
+ opt.tags = False
+ opt.optimized_fetch = False
+ opt.retry_fetches = 0
+ opt.prune = False
+ opt.detach_head = False
+ opt.force_checkout = False
+ opt.rebase = False
+ return opt
+
+ def test_worker_successful_sync(self):
+ """Test _SyncProjectList with a successful fetch and checkout."""
+ opt = self._get_opts()
+ project = self.projA
+ project.Sync_NetworkHalf = mock.Mock(
+ return_value=SyncNetworkHalfResult(error=None, remote_fetched=True)
+ )
+ project.Sync_LocalHalf = mock.Mock()
+ project.manifest.manifestProject.config = mock.MagicMock()
+ self.mock_context["projects"] = [project]
+
+ with mock.patch("subcmds.sync.SyncBuffer") as mock_sync_buffer:
+ mock_sync_buf_instance = mock.MagicMock()
+ mock_sync_buf_instance.Finish.return_value = True
+ mock_sync_buffer.return_value = mock_sync_buf_instance
+
+ result_obj = self.cmd._SyncProjectList(opt, [0])
+
+ self.assertEqual(len(result_obj.results), 1)
+ result = result_obj.results[0]
+ self.assertTrue(result.fetch_success)
+ self.assertTrue(result.checkout_success)
+ self.assertIsNone(result.fetch_error)
+ self.assertIsNone(result.checkout_error)
+ project.Sync_NetworkHalf.assert_called_once()
+ project.Sync_LocalHalf.assert_called_once()
+
+ def test_worker_fetch_fails(self):
+ """Test _SyncProjectList with a failed fetch."""
+ opt = self._get_opts()
+ project = self.projA
+ fetch_error = GitError("Fetch failed")
+ project.Sync_NetworkHalf = mock.Mock(
+ return_value=SyncNetworkHalfResult(
+ error=fetch_error, remote_fetched=False
+ )
+ )
+ project.Sync_LocalHalf = mock.Mock()
+ self.mock_context["projects"] = [project]
+
+ result_obj = self.cmd._SyncProjectList(opt, [0])
+ result = result_obj.results[0]
+
+ self.assertFalse(result.fetch_success)
+ self.assertFalse(result.checkout_success)
+ self.assertEqual(result.fetch_error, fetch_error)
+ self.assertIsNone(result.checkout_error)
+ project.Sync_NetworkHalf.assert_called_once()
+ project.Sync_LocalHalf.assert_not_called()
+
+ def test_worker_fetch_fails_exception(self):
+ """Test _SyncProjectList with an exception during fetch."""
+ opt = self._get_opts()
+ project = self.projA
+ fetch_error = GitError("Fetch failed")
+ project.Sync_NetworkHalf = mock.Mock(side_effect=fetch_error)
+ project.Sync_LocalHalf = mock.Mock()
+ self.mock_context["projects"] = [project]
+
+ result_obj = self.cmd._SyncProjectList(opt, [0])
+ result = result_obj.results[0]
+
+ self.assertFalse(result.fetch_success)
+ self.assertFalse(result.checkout_success)
+ self.assertEqual(result.fetch_error, fetch_error)
+ project.Sync_NetworkHalf.assert_called_once()
+ project.Sync_LocalHalf.assert_not_called()
+
+ def test_worker_checkout_fails(self):
+ """Test _SyncProjectList with an exception during checkout."""
+ opt = self._get_opts()
+ project = self.projA
+ project.Sync_NetworkHalf = mock.Mock(
+ return_value=SyncNetworkHalfResult(error=None, remote_fetched=True)
+ )
+ checkout_error = GitError("Checkout failed")
+ project.Sync_LocalHalf = mock.Mock(side_effect=checkout_error)
+ project.manifest.manifestProject.config = mock.MagicMock()
+ self.mock_context["projects"] = [project]
+
+ with mock.patch("subcmds.sync.SyncBuffer"):
+ result_obj = self.cmd._SyncProjectList(opt, [0])
+ result = result_obj.results[0]
+
+ self.assertTrue(result.fetch_success)
+ self.assertFalse(result.checkout_success)
+ self.assertIsNone(result.fetch_error)
+ self.assertEqual(result.checkout_error, checkout_error)
+ project.Sync_NetworkHalf.assert_called_once()
+ project.Sync_LocalHalf.assert_called_once()
+
+ def test_worker_local_only(self):
+ """Test _SyncProjectList with --local-only."""
+ opt = self._get_opts(["--interleaved", "--local-only"])
+ project = self.projA
+ project.Sync_NetworkHalf = mock.Mock()
+ project.Sync_LocalHalf = mock.Mock()
+ project.manifest.manifestProject.config = mock.MagicMock()
+ self.mock_context["projects"] = [project]
+
+ with mock.patch("subcmds.sync.SyncBuffer") as mock_sync_buffer:
+ mock_sync_buf_instance = mock.MagicMock()
+ mock_sync_buf_instance.Finish.return_value = True
+ mock_sync_buffer.return_value = mock_sync_buf_instance
+
+ result_obj = self.cmd._SyncProjectList(opt, [0])
+ result = result_obj.results[0]
+
+ self.assertTrue(result.fetch_success)
+ self.assertTrue(result.checkout_success)
+ project.Sync_NetworkHalf.assert_not_called()
+ project.Sync_LocalHalf.assert_called_once()
+
+ def test_worker_network_only(self):
+ """Test _SyncProjectList with --network-only."""
+ opt = self._get_opts(["--interleaved", "--network-only"])
+ project = self.projA
+ project.Sync_NetworkHalf = mock.Mock(
+ return_value=SyncNetworkHalfResult(error=None, remote_fetched=True)
+ )
+ project.Sync_LocalHalf = mock.Mock()
+ self.mock_context["projects"] = [project]
+
+ result_obj = self.cmd._SyncProjectList(opt, [0])
+ result = result_obj.results[0]
+
+ self.assertTrue(result.fetch_success)
+ self.assertTrue(result.checkout_success)
+ project.Sync_NetworkHalf.assert_called_once()
+ project.Sync_LocalHalf.assert_not_called()