diff options
| author | Gavin Mak <gavinmak@google.com> | 2026-04-22 00:39:42 +0000 |
|---|---|---|
| committer | gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2026-05-20 12:28:55 -0700 |
| commit | 2d54384a5e59fddd0626ec091cc0ad11511d834e (patch) | |
| tree | c0a9d642bb4064a23df2044777b6f81f2c047473 /tests | |
| parent | 1b4e7a04be7b49d8c0c5e161d36f209bca4b6498 (diff) | |
| download | git-repo-stable.tar.gz | |
Allow syncing the outer manifest to a state defined by a specific
superproject revision. It updates the superproject, reads the manifest
commit from .supermanifest, and checks out the outer manifest project
to that commit.
Submanifests are then processed normally, allowing them to be updated
to the revisions specified in the new outer manifest state.
Bug: 416589884
Change-Id: I304c37a2b8794f9b74cb7e5e209a8a93762bdb52
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/576321
Commit-Queue: Gavin Mak <gavinmak@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_subcmds_sync.py | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index ef162392b..785c0e629 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py | |||
| @@ -1286,3 +1286,171 @@ class UpdateCopyLinkfileListTest(unittest.TestCase): | |||
| 1286 | self.assertFalse(os.path.lexists(os.path.join(llms_dir, "rules"))) | 1286 | self.assertFalse(os.path.lexists(os.path.join(llms_dir, "rules"))) |
| 1287 | self.assertTrue(os.path.exists(os.path.join(llms_dir, "my-notes.txt"))) | 1287 | self.assertTrue(os.path.exists(os.path.join(llms_dir, "my-notes.txt"))) |
| 1288 | self.assertTrue(os.path.isdir(llms_dir)) | 1288 | self.assertTrue(os.path.isdir(llms_dir)) |
| 1289 | |||
| 1290 | |||
| 1291 | class SyncToSuperprojectRevTests(unittest.TestCase): | ||
| 1292 | """Tests for Sync._SyncToSuperprojectRev.""" | ||
| 1293 | |||
| 1294 | def setUp(self): | ||
| 1295 | self.repodir = tempfile.mkdtemp(".repo") | ||
| 1296 | self.manifest = mock.MagicMock(repodir=self.repodir) | ||
| 1297 | self.manifest.superproject = mock.MagicMock() | ||
| 1298 | self.manifest.path_prefix = "" | ||
| 1299 | |||
| 1300 | self.mp = mock.MagicMock() | ||
| 1301 | self.cmd = sync.Sync(manifest=self.manifest) | ||
| 1302 | self.cmd.outer_manifest = self.manifest | ||
| 1303 | |||
| 1304 | self.opt = mock.Mock() | ||
| 1305 | self.opt.verbose = False | ||
| 1306 | self.opt.superproject_revision = "deadbeef" | ||
| 1307 | self.opt.mp_update = True | ||
| 1308 | |||
| 1309 | self.errors = [] | ||
| 1310 | |||
| 1311 | def tearDown(self): | ||
| 1312 | shutil.rmtree(self.repodir) | ||
| 1313 | |||
| 1314 | @mock.patch("subcmds.sync.GitCommand") | ||
| 1315 | def test_successful_sync(self, mock_git_command): | ||
| 1316 | """Test successful sync to superproject rev.""" | ||
| 1317 | mock_superproject = self.manifest.superproject | ||
| 1318 | mock_superproject.Sync.return_value = mock.Mock(success=True) | ||
| 1319 | |||
| 1320 | mock_git = mock.Mock() | ||
| 1321 | mock_git.Wait.return_value = 0 | ||
| 1322 | mock_git.stdout = "proj branch manifest_commit_hash\n" | ||
| 1323 | mock_git_command.return_value = mock_git | ||
| 1324 | |||
| 1325 | with mock.patch.object( | ||
| 1326 | self.cmd, "_UpdateManifestProject" | ||
| 1327 | ) as mock_update: | ||
| 1328 | self.cmd._SyncToSuperprojectRev( | ||
| 1329 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1330 | ) | ||
| 1331 | |||
| 1332 | mock_superproject.SetRevisionId.assert_called_with("deadbeef") | ||
| 1333 | mock_superproject.Sync.assert_called_once() | ||
| 1334 | mock_git_command.assert_called_once() | ||
| 1335 | self.mp.SetRevision.assert_called_with("manifest_commit_hash") | ||
| 1336 | mock_update.assert_called_once() | ||
| 1337 | self.assertEqual(self.errors, []) | ||
| 1338 | |||
| 1339 | @mock.patch("subcmds.sync.GitCommand") | ||
| 1340 | def test_parse_error(self, mock_git_command): | ||
| 1341 | """Test error when .supermanifest cannot be parsed.""" | ||
| 1342 | mock_superproject = self.manifest.superproject | ||
| 1343 | mock_superproject.Sync.return_value = mock.Mock(success=True) | ||
| 1344 | |||
| 1345 | mock_git = mock.Mock() | ||
| 1346 | mock_git.Wait.return_value = 0 | ||
| 1347 | # Invalid format (not 3 parts) | ||
| 1348 | mock_git.stdout = "invalid_content\n" | ||
| 1349 | mock_git_command.return_value = mock_git | ||
| 1350 | |||
| 1351 | with self.assertRaises(sync.SyncError) as e: | ||
| 1352 | self.cmd._SyncToSuperprojectRev( | ||
| 1353 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1354 | ) | ||
| 1355 | self.assertIn("could not parse .supermanifest", str(e.exception)) | ||
| 1356 | |||
| 1357 | @mock.patch("subcmds.sync.GitCommand") | ||
| 1358 | def test_read_error(self, mock_git_command): | ||
| 1359 | """Test error when reading .supermanifest fails.""" | ||
| 1360 | mock_superproject = self.manifest.superproject | ||
| 1361 | mock_superproject.Sync.return_value = mock.Mock(success=True) | ||
| 1362 | |||
| 1363 | mock_git = mock.Mock() | ||
| 1364 | mock_git.Wait.return_value = 1 | ||
| 1365 | mock_git.stderr = "git error" | ||
| 1366 | mock_git_command.return_value = mock_git | ||
| 1367 | |||
| 1368 | with self.assertRaises(sync.SyncError) as e: | ||
| 1369 | self.cmd._SyncToSuperprojectRev( | ||
| 1370 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1371 | ) | ||
| 1372 | self.assertIn("failed to read .supermanifest", str(e.exception)) | ||
| 1373 | |||
| 1374 | def test_no_superproject(self): | ||
| 1375 | """Test error when superproject is not defined.""" | ||
| 1376 | self.manifest.superproject = None | ||
| 1377 | |||
| 1378 | with self.assertRaises(sync.SyncError) as e: | ||
| 1379 | self.cmd._SyncToSuperprojectRev( | ||
| 1380 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1381 | ) | ||
| 1382 | self.assertIn("superproject not defined", str(e.exception)) | ||
| 1383 | |||
| 1384 | @mock.patch("subcmds.sync.GitCommand") | ||
| 1385 | def test_sync_failure(self, mock_git_command): | ||
| 1386 | """Test error when superproject sync fails.""" | ||
| 1387 | mock_superproject = self.manifest.superproject | ||
| 1388 | mock_superproject.Sync.return_value = mock.Mock(success=False) | ||
| 1389 | |||
| 1390 | with self.assertRaises(sync.SyncError) as e: | ||
| 1391 | self.cmd._SyncToSuperprojectRev( | ||
| 1392 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1393 | ) | ||
| 1394 | self.assertIn("failed to sync superproject", str(e.exception)) | ||
| 1395 | |||
| 1396 | |||
| 1397 | class UpdateAllManifestProjectsTests(unittest.TestCase): | ||
| 1398 | """Tests for Sync._UpdateAllManifestProjects.""" | ||
| 1399 | |||
| 1400 | def setUp(self): | ||
| 1401 | self.repodir = tempfile.mkdtemp(".repo") | ||
| 1402 | self.manifest = mock.MagicMock(repodir=self.repodir) | ||
| 1403 | self.manifest.superproject = mock.MagicMock() | ||
| 1404 | self.manifest.path_prefix = "" | ||
| 1405 | self.manifest.standalone_manifest_url = None | ||
| 1406 | self.manifest.submanifests = {} | ||
| 1407 | |||
| 1408 | self.mp = mock.MagicMock() | ||
| 1409 | self.mp.manifest = self.manifest | ||
| 1410 | self.mp.standalone_manifest_url = None | ||
| 1411 | self.cmd = sync.Sync(manifest=self.manifest) | ||
| 1412 | self.cmd.outer_manifest = self.manifest | ||
| 1413 | |||
| 1414 | self.opt = mock.Mock() | ||
| 1415 | self.opt.verbose = False | ||
| 1416 | self.opt.superproject_revision = None | ||
| 1417 | self.opt.mp_update = True | ||
| 1418 | |||
| 1419 | self.errors = [] | ||
| 1420 | |||
| 1421 | def tearDown(self): | ||
| 1422 | shutil.rmtree(self.repodir) | ||
| 1423 | |||
| 1424 | def test_superproject_revision_outer_manifest(self): | ||
| 1425 | """Test that _SyncToSuperprojectRev is called for outer manifest.""" | ||
| 1426 | self.opt.superproject_revision = "deadbeef" | ||
| 1427 | |||
| 1428 | with mock.patch.object( | ||
| 1429 | self.cmd, "_SyncToSuperprojectRev" | ||
| 1430 | ) as mock_sync_to_rev: | ||
| 1431 | self.cmd._UpdateAllManifestProjects( | ||
| 1432 | self.opt, self.mp, "name", self.errors | ||
| 1433 | ) | ||
| 1434 | mock_sync_to_rev.assert_called_once_with( | ||
| 1435 | self.opt, self.manifest, self.mp, "name", self.errors | ||
| 1436 | ) | ||
| 1437 | |||
| 1438 | def test_superproject_revision_submanifest(self): | ||
| 1439 | """Test that _SyncToSuperprojectRev is NOT called for submanifest.""" | ||
| 1440 | self.opt.superproject_revision = "deadbeef" | ||
| 1441 | submanifest = mock.MagicMock() | ||
| 1442 | submanifest.path_prefix = "sub/" | ||
| 1443 | submanifest.standalone_manifest_url = None | ||
| 1444 | self.mp.manifest = submanifest | ||
| 1445 | |||
| 1446 | with mock.patch.object( | ||
| 1447 | self.cmd, "_SyncToSuperprojectRev" | ||
| 1448 | ) as mock_sync_to_rev: | ||
| 1449 | with mock.patch.object( | ||
| 1450 | self.cmd, "_UpdateManifestProject" | ||
| 1451 | ) as mock_update_manifest: | ||
| 1452 | self.cmd._UpdateAllManifestProjects( | ||
| 1453 | self.opt, self.mp, "name", self.errors | ||
| 1454 | ) | ||
| 1455 | mock_sync_to_rev.assert_not_called() | ||
| 1456 | mock_update_manifest.assert_called_once() | ||
