diff options
Diffstat (limited to 'tests/test_subcmds_sync.py')
| -rw-r--r-- | tests/test_subcmds_sync.py | 124 |
1 files changed, 123 insertions, 1 deletions
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index b871317c..60f283af 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py | |||
| @@ -305,8 +305,10 @@ class LocalSyncState(unittest.TestCase): | |||
| 305 | 305 | ||
| 306 | 306 | ||
| 307 | class FakeProject: | 307 | class FakeProject: |
| 308 | def __init__(self, relpath): | 308 | def __init__(self, relpath, name=None, objdir=None): |
| 309 | self.relpath = relpath | 309 | self.relpath = relpath |
| 310 | self.name = name or relpath | ||
| 311 | self.objdir = objdir or relpath | ||
| 310 | 312 | ||
| 311 | def __str__(self): | 313 | def __str__(self): |
| 312 | return f"project: {self.relpath}" | 314 | return f"project: {self.relpath}" |
| @@ -513,3 +515,123 @@ class SyncCommand(unittest.TestCase): | |||
| 513 | self.cmd.Execute(self.opt, []) | 515 | self.cmd.Execute(self.opt, []) |
| 514 | self.assertIn(self.sync_local_half_error, e.aggregate_errors) | 516 | self.assertIn(self.sync_local_half_error, e.aggregate_errors) |
| 515 | self.assertIn(self.sync_network_half_error, e.aggregate_errors) | 517 | self.assertIn(self.sync_network_half_error, e.aggregate_errors) |
| 518 | |||
| 519 | |||
| 520 | class InterleavedSyncTest(unittest.TestCase): | ||
| 521 | """Tests for interleaved sync.""" | ||
| 522 | |||
| 523 | def setUp(self): | ||
| 524 | """Set up a sync command with mocks.""" | ||
| 525 | self.repodir = tempfile.mkdtemp(".repo") | ||
| 526 | self.manifest = mock.MagicMock(repodir=self.repodir) | ||
| 527 | self.manifest.repoProject.LastFetch = time.time() | ||
| 528 | self.manifest.repoProject.worktree = self.repodir | ||
| 529 | self.manifest.manifestProject.worktree = self.repodir | ||
| 530 | self.manifest.IsArchive = False | ||
| 531 | self.manifest.CloneBundle = False | ||
| 532 | self.manifest.default.sync_j = 1 | ||
| 533 | |||
| 534 | self.cmd = sync.Sync(manifest=self.manifest) | ||
| 535 | self.cmd.outer_manifest = self.manifest | ||
| 536 | |||
| 537 | # Mock projects. | ||
| 538 | self.projA = FakeProject("projA", objdir="objA") | ||
| 539 | self.projB = FakeProject("projB", objdir="objB") | ||
| 540 | self.projA_sub = FakeProject( | ||
| 541 | "projA/sub", name="projA_sub", objdir="objA_sub" | ||
| 542 | ) | ||
| 543 | self.projC = FakeProject("projC", objdir="objC") | ||
| 544 | |||
| 545 | # Mock methods that are not part of the core interleaved sync logic. | ||
| 546 | mock.patch.object(self.cmd, "_UpdateAllManifestProjects").start() | ||
| 547 | mock.patch.object(self.cmd, "_UpdateProjectsRevisionId").start() | ||
| 548 | mock.patch.object(self.cmd, "_ValidateOptionsWithManifest").start() | ||
| 549 | mock.patch.object(sync, "_PostRepoUpgrade").start() | ||
| 550 | mock.patch.object(sync, "_PostRepoFetch").start() | ||
| 551 | |||
| 552 | def tearDown(self): | ||
| 553 | """Clean up resources.""" | ||
| 554 | shutil.rmtree(self.repodir) | ||
| 555 | mock.patch.stopall() | ||
| 556 | |||
| 557 | def test_interleaved_fail_fast(self): | ||
| 558 | """Test that --fail-fast is respected in interleaved mode.""" | ||
| 559 | opt, args = self.cmd.OptionParser.parse_args( | ||
| 560 | ["--interleaved", "--fail-fast", "-j2"] | ||
| 561 | ) | ||
| 562 | opt.quiet = True | ||
| 563 | |||
| 564 | # With projA/sub, _SafeCheckoutOrder creates two batches: | ||
| 565 | # 1. [projA, projB] | ||
| 566 | # 2. [projA/sub] | ||
| 567 | # We want to fail on the first batch and ensure the second isn't run. | ||
| 568 | all_projects = [self.projA, self.projB, self.projA_sub] | ||
| 569 | mock.patch.object( | ||
| 570 | self.cmd, "GetProjects", return_value=all_projects | ||
| 571 | ).start() | ||
| 572 | |||
| 573 | # Mock ExecuteInParallel to simulate a failed run on the first batch of | ||
| 574 | # projects. | ||
| 575 | execute_mock = mock.patch.object( | ||
| 576 | self.cmd, "ExecuteInParallel", return_value=False | ||
| 577 | ).start() | ||
| 578 | |||
| 579 | with self.assertRaises(sync.SyncFailFastError): | ||
| 580 | self.cmd._SyncInterleaved( | ||
| 581 | opt, | ||
| 582 | args, | ||
| 583 | [], | ||
| 584 | self.manifest, | ||
| 585 | self.manifest.manifestProject, | ||
| 586 | all_projects, | ||
| 587 | {}, | ||
| 588 | ) | ||
| 589 | |||
| 590 | execute_mock.assert_called_once() | ||
| 591 | |||
| 592 | def test_interleaved_shared_objdir_serial(self): | ||
| 593 | """Test that projects with shared objdir are processed serially.""" | ||
| 594 | opt, args = self.cmd.OptionParser.parse_args(["--interleaved", "-j4"]) | ||
| 595 | opt.quiet = True | ||
| 596 | |||
| 597 | # Setup projects with a shared objdir. | ||
| 598 | self.projA.objdir = "common_objdir" | ||
| 599 | self.projC.objdir = "common_objdir" | ||
| 600 | |||
| 601 | all_projects = [self.projA, self.projB, self.projC] | ||
| 602 | mock.patch.object( | ||
| 603 | self.cmd, "GetProjects", return_value=all_projects | ||
| 604 | ).start() | ||
| 605 | |||
| 606 | def execute_side_effect(jobs, target, work_items, **kwargs): | ||
| 607 | # The callback is a partial object. The first arg is the set we | ||
| 608 | # need to update to avoid the stall detection. | ||
| 609 | synced_relpaths_set = kwargs["callback"].args[0] | ||
| 610 | projects_in_pass = self.cmd.get_parallel_context()["projects"] | ||
| 611 | for item in work_items: | ||
| 612 | for project_idx in item: | ||
| 613 | synced_relpaths_set.add( | ||
| 614 | projects_in_pass[project_idx].relpath | ||
| 615 | ) | ||
| 616 | return True | ||
| 617 | |||
| 618 | execute_mock = mock.patch.object( | ||
| 619 | self.cmd, "ExecuteInParallel", side_effect=execute_side_effect | ||
| 620 | ).start() | ||
| 621 | |||
| 622 | self.cmd._SyncInterleaved( | ||
| 623 | opt, | ||
| 624 | args, | ||
| 625 | [], | ||
| 626 | self.manifest, | ||
| 627 | self.manifest.manifestProject, | ||
| 628 | all_projects, | ||
| 629 | {}, | ||
| 630 | ) | ||
| 631 | |||
| 632 | execute_mock.assert_called_once() | ||
| 633 | jobs_arg, _, work_items = execute_mock.call_args.args | ||
| 634 | self.assertEqual(jobs_arg, 2) | ||
| 635 | work_items_sets = {frozenset(item) for item in work_items} | ||
| 636 | expected_sets = {frozenset([0, 2]), frozenset([1])} | ||
| 637 | self.assertEqual(work_items_sets, expected_sets) | ||
