repo_sync_manifest: Rework repo_sync_manifest.

This script now supports syncing based on a variety of manifest
sources (branch, buildspecs (based on manifest-verions), or a local
manifest file). It also supports repo preloading, and git cache
optimization hints.

This switches the script from the repo_util library to the older
repository library.

It also enforces some cleanup of existing checkouts. Source will be
detached, and any existing local branches will be deleted. Files not
under verssion control will NOT be cleaned up, and other expensive
cleaning operations (deleting git locks) will not be performed.

It is NOT safe to assume that the checkout can be further updated with
a simple "repo sync", instead you should call this script again with
the same options.

Additional CLs are coming which will add support for cherry-picking
changes into the checkout during sync.

BUG=chromium:864816
TEST=run_tests

Change-Id: If8cd16c60f417e07d16d099d948b8ce53e4afdf6
Reviewed-on: https://chromium-review.googlesource.com/c/1316817
Tested-by: Don Garrett <dgarrett@chromium.org>
Reviewed-by: Alec Thilenius <athilenius@google.com>
diff --git a/scripts/repo_sync_manifest_unittest.py b/scripts/repo_sync_manifest_unittest.py
index 5da0659..ad7630c 100644
--- a/scripts/repo_sync_manifest_unittest.py
+++ b/scripts/repo_sync_manifest_unittest.py
@@ -7,39 +7,275 @@
 
 from __future__ import print_function
 
+import mock
 import os
 
+from chromite.cbuildbot import manifest_version
+from chromite.cbuildbot import repository
 from chromite.lib import cros_test_lib
-from chromite.lib import osutils
-from chromite.lib import repo_util
-from chromite.lib import repo_util_unittest
 from chromite.scripts import repo_sync_manifest
 
 
-class RepoSyncManifestTest(cros_test_lib.MockTempDirTestCase):
+class RepoSyncManifestTest(cros_test_lib.RunCommandTempDirTestCase):
   """Unit tests for repo_sync_manifest."""
 
+  INT_MANIFEST_URL = ('https://chrome-internal-review.googlesource.com/'
+                      'chromeos/manifest-internal')
+  EXT_MANIFEST_URL = ('https://chromium.googlesource.com/chromiumos/manifest')
+
+  MV_INT_URL = ('https://chrome-internal.googlesource.com/chromeos/'
+                'manifest-versions')
+  MV_EXT_URL = ('https://chromium.googlesource.com/chromiumos/'
+                'manifest-versions')
+
   def setUp(self):
+    self.repo_dir = os.path.join(self.tempdir, 'repo')
+    self.preload_src = os.path.join(self.tempdir, 'source')
+    self.git_cache = os.path.join(self.tempdir, 'git_cache')
     self.manifest = os.path.join(self.tempdir, 'manifest.xml')
-    osutils.Touch(self.manifest)
+    self.mv_ext = os.path.join(self.tempdir, 'mv_ext')
+    self.mv_int = os.path.join(self.tempdir, 'mv_int')
 
-    self.repo_root = os.path.join(self.tempdir, 'root')
-    os.makedirs(os.path.join(self.repo_root, '.repo'))
+    self.repo_mock = self.PatchObject(
+        repository, 'RepoRepository', autospec=True)
 
-    self.empty_root = os.path.join(self.tempdir, 'empty')
-    os.makedirs(os.path.join(self.empty_root))
+    self.refresh_manifest_mock = self.PatchObject(
+        manifest_version, 'RefreshManifestCheckout', autospec=True)
 
-    self.mock_copy = self.PatchObject(
-        repo_util.Repository, 'Copy',
-        side_effect=repo_util_unittest.CopySideEffects)
-    self.mock_sync = self.PatchObject(repo_util.Repository, 'Sync')
+    self.resolve_buildspec_mock = self.PatchObject(
+        manifest_version, 'ResolveBuildspec', autospec=True)
 
-  def testRepoRoot(self):
-    repo_sync_manifest.main([self.manifest, '--repo-root', self.repo_root])
-    self.mock_sync.assert_called_with(manifest_path=self.manifest)
+    self.resolve_version_mock = self.PatchObject(
+        manifest_version, 'ResolveBuildspecVersion', autospec=True)
 
-  def testCopyRepo(self):
-    repo_sync_manifest.main([self.manifest, '--repo-root', self.empty_root,
-                             '--copy-repo', self.repo_root])
-    self.mock_copy.assert_called_with(self.empty_root)
-    self.mock_sync.assert_called_with(manifest_path=self.manifest)
+
+  def notestHelp(self):
+    with self.assertRaises(SystemExit):
+      repo_sync_manifest.main([
+          '--help',
+      ])
+
+  def testMinimal(self):
+    repo_sync_manifest.main([
+        '--repo-root', self.repo_dir,
+    ])
+
+    # Ensure manifest_versions is not updated.
+    self.assertEqual(self.refresh_manifest_mock.mock_calls, [])
+
+    # Ensure RepoRepository created and Sync'd as expected.
+    self.assertEqual(self.repo_mock.mock_calls, [
+        mock.call(
+            directory=self.repo_dir,
+            manifest_repo_url=self.INT_MANIFEST_URL,
+            branch='master',
+            git_cache_dir=None,
+        ),
+        mock.call().Sync(detach=True, local_manifest=None)
+    ])
+
+  def testMinimalExternal(self):
+    repo_sync_manifest.main([
+        '--repo-root', self.repo_dir,
+        '--external',
+    ])
+
+    # Ensure manifest_versions is not updated.
+    self.assertEqual(self.refresh_manifest_mock.mock_calls, [])
+
+    # Ensure RepoRepository created and Sync'd as expected.
+    self.assertEqual(self.repo_mock.mock_calls, [
+        mock.call(
+            directory=self.repo_dir,
+            manifest_repo_url=self.EXT_MANIFEST_URL,
+            branch='master',
+            git_cache_dir=None,
+        ),
+        mock.call().Sync(detach=True, local_manifest=None)
+    ])
+
+  def testBranch(self):
+    repo_sync_manifest.main([
+        '--repo-root', self.repo_dir,
+        '--branch', 'branch'
+    ])
+
+    # Ensure RepoRepository created and Sync'd as expected.
+    self.assertEqual(self.repo_mock.mock_calls, [
+        mock.call(
+            directory=self.repo_dir,
+            manifest_repo_url=self.INT_MANIFEST_URL,
+            branch='branch',
+            git_cache_dir=None,
+        ),
+        mock.call().Sync(detach=True, local_manifest=None)
+    ])
+
+    # Ensure manifest_versions is not updated.
+    self.assertEqual(self.refresh_manifest_mock.mock_calls, [])
+
+  def testBuildSpec(self):
+    self.resolve_buildspec_mock.return_value = 'resolved_buildspec'
+
+    repo_sync_manifest.main([
+        '--repo-root', self.repo_dir,
+        '--buildspec', 'test/spec',
+        '--manifest-versions-int', self.mv_int,
+    ])
+
+    # Ensure manifest-versions interactions are correct.
+    self.assertEqual(self.refresh_manifest_mock.mock_calls, [
+        mock.call(self.mv_int, self.MV_INT_URL)])
+    self.assertEqual(self.resolve_buildspec_mock.mock_calls, [
+        mock.call(self.mv_int, 'test/spec')])
+    self.assertEqual(self.resolve_version_mock.mock_calls, [])
+
+    # Ensure RepoRepository created and Sync'd as expected.
+    self.assertEqual(self.repo_mock.mock_calls, [
+        mock.call(
+            directory=self.repo_dir,
+            manifest_repo_url=self.INT_MANIFEST_URL,
+            branch='master',
+            git_cache_dir=None,
+        ),
+        mock.call().Sync(detach=True, local_manifest='resolved_buildspec')
+    ])
+
+  def testBuildSpecExternal(self):
+    self.resolve_buildspec_mock.return_value = 'resolved_buildspec'
+
+    repo_sync_manifest.main([
+        '--repo-root', self.repo_dir,
+        '--buildspec', 'test/spec',
+        '--manifest-versions-ext', self.mv_ext,
+        '--external',
+    ])
+
+    # Ensure manifest-versions interactions are correct.
+    self.assertEqual(self.refresh_manifest_mock.mock_calls, [
+        mock.call(self.mv_ext, self.MV_EXT_URL)])
+    self.assertEqual(self.resolve_buildspec_mock.mock_calls, [
+        mock.call(self.mv_ext, 'test/spec')])
+    self.assertEqual(self.resolve_version_mock.mock_calls, [])
+
+    # Ensure RepoRepository created and Sync'd as expected.
+    self.assertEqual(self.repo_mock.mock_calls, [
+        mock.call(
+            directory=self.repo_dir,
+            manifest_repo_url=self.EXT_MANIFEST_URL,
+            branch='master',
+            git_cache_dir=None,
+        ),
+        mock.call().Sync(detach=True, local_manifest='resolved_buildspec')
+    ])
+
+  def testVersion(self):
+    self.resolve_version_mock.return_value = 'resolved_buildspec'
+
+    repo_sync_manifest.main([
+        '--repo-root', self.repo_dir,
+        '--version', '1.2.3',
+        '--manifest-versions-int', self.mv_int,
+    ])
+
+    # Ensure manifest-versions interactions are correct.
+    self.assertEqual(self.refresh_manifest_mock.mock_calls, [
+        mock.call(self.mv_int, self.MV_INT_URL)])
+    self.assertEqual(self.resolve_buildspec_mock.mock_calls, [])
+    self.assertEqual(self.resolve_version_mock.mock_calls, [
+        mock.call(self.mv_int, '1.2.3')])
+
+    # Ensure RepoRepository created and Sync'd as expected.
+    self.assertEqual(self.repo_mock.mock_calls, [
+        mock.call(
+            directory=self.repo_dir,
+            manifest_repo_url=self.INT_MANIFEST_URL,
+            branch='master',
+            git_cache_dir=None,
+        ),
+        mock.call().Sync(detach=True, local_manifest='resolved_buildspec')
+    ])
+
+  def testVersionExternal(self):
+    self.resolve_version_mock.return_value = 'resolved_buildspec'
+
+    repo_sync_manifest.main([
+        '--repo-root', self.repo_dir,
+        '--version', '1.2.3',
+        '--manifest-versions-ext', self.mv_ext,
+        '--external',
+    ])
+
+    # Ensure manifest-versions interactions are correct.
+    self.assertEqual(self.refresh_manifest_mock.mock_calls, [
+        mock.call(self.mv_ext, self.MV_EXT_URL)])
+    self.assertEqual(self.resolve_buildspec_mock.mock_calls, [])
+    self.assertEqual(self.resolve_version_mock.mock_calls, [
+        mock.call(self.mv_ext, '1.2.3')])
+
+    # Ensure RepoRepository created and Sync'd as expected.
+    self.assertEqual(self.repo_mock.mock_calls, [
+        mock.call(
+            directory=self.repo_dir,
+            manifest_repo_url=self.EXT_MANIFEST_URL,
+            branch='master',
+            git_cache_dir=None,
+        ),
+        mock.call().Sync(detach=True, local_manifest='resolved_buildspec')
+    ])
+
+  def testBuildSpecNoManifestVersions(self):
+    with self.assertRaises(AssertionError):
+      repo_sync_manifest.main([
+          '--repo-root', self.repo_dir,
+          '--buildspec', 'test/spec',
+      ])
+
+    with self.assertRaises(AssertionError):
+      repo_sync_manifest.main([
+          '--repo-root', self.repo_dir,
+          '--buildspec', 'test/spec',
+          '--external',
+      ])
+
+  def testLocalManifest(self):
+    repo_sync_manifest.main([
+        '--repo-root', self.repo_dir,
+        '--manifest-file', self.manifest,
+    ])
+
+    # Ensure manifest_versions is not updated.
+    self.assertEqual(self.refresh_manifest_mock.mock_calls, [])
+
+    # Ensure RepoRepository created and Sync'd as expected.
+    self.assertEqual(self.repo_mock.mock_calls, [
+        mock.call(
+            directory=self.repo_dir,
+            manifest_repo_url=self.INT_MANIFEST_URL,
+            branch='master',
+            git_cache_dir=None,
+        ),
+        mock.call().Sync(detach=True, local_manifest=self.manifest)
+    ])
+
+  def testOptimizations(self):
+    repo_sync_manifest.main([
+        '--repo-root', self.repo_dir,
+        '--copy-repo', self.preload_src,
+        '--git-cache', self.git_cache,
+    ])
+
+    # Ensure manifest_versions is not updated.
+    self.assertEqual(self.refresh_manifest_mock.mock_calls, [])
+
+    # Ensure RepoRepository created and Sync'd as expected.
+    self.assertEqual(self.repo_mock.mock_calls, [
+        mock.call(
+            directory=self.repo_dir,
+            manifest_repo_url=self.INT_MANIFEST_URL,
+            branch='master',
+            git_cache_dir=self.git_cache,
+        ),
+        mock.call().PreLoad(self.preload_src),
+        mock.call().Sync(detach=True, local_manifest=None)
+    ])