2 # Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Unittests for the branch stages."""
8 from __future__ import print_function
13 sys.path.insert(0, os.path.abspath('%s/../../..' % os.path.dirname(__file__)))
14 from chromite.cbuildbot import constants
15 from chromite.cbuildbot import manifest_version
16 from chromite.cbuildbot import manifest_version_unittest
17 from chromite.cbuildbot.stages import branch_stages
18 from chromite.cbuildbot.stages import generic_stages_unittest
19 from chromite.lib import cros_build_lib
20 from chromite.lib import cros_build_lib_unittest
21 from chromite.lib import cros_test_lib
22 from chromite.lib import git
23 from chromite.lib import git_unittest
24 from chromite.lib import osutils
25 from chromite.lib import parallel_unittest
26 from chromite.lib import partial_mock
29 MANIFEST_CONTENTS = """\
30 <?xml version="1.0" encoding="UTF-8"?>
32 <remote fetch="https://chromium.googlesource.com"
34 review="chromium-review.googlesource.com"/>
36 <default remote="cros" revision="refs/heads/master" sync-j="8"/>
38 <project groups="minilayout,buildtools"
39 name="chromiumos/chromite"
41 revision="refs/heads/special-branch"/>
43 <project name="chromiumos/special"
44 path="src/special-new"
45 revision="new-special-branch"/>
47 <project name="chromiumos/special"
48 path="src/special-old"
49 revision="old-special-branch"/>
51 <project name="faraway/external"
53 revision="refs/heads/master"/>
55 <project name="faraway/unpinned"
57 revision="refs/heads/master"
62 CHROMITE_REVISION = "fb46d34d7cd4b9c167b74f494f2a99b68df50b18"
63 SPECIAL_REVISION1 = "7bc42f093d644eeaf1c77fab60883881843c3c65"
64 SPECIAL_REVISION2 = "6270eb3b4f78d9bffec77df50f374f5aae72b370"
66 VERSIONED_MANIFEST_CONTENTS = """\
67 <?xml version="1.0" encoding="UTF-8"?>
68 <manifest revision="fe72f0912776fa4596505e236e39286fb217961b">
69 <remote fetch="https://chrome-internal.googlesource.com" name="chrome"/>
70 <remote fetch="https://chromium.googlesource.com/" name="chromium"/>
71 <remote fetch="https://chromium.googlesource.com" name="cros" \
72 review="chromium-review.googlesource.com"/>
73 <remote fetch="https://chrome-internal.googlesource.com" name="cros-internal" \
74 review="https://chrome-internal-review.googlesource.com"/>
75 <remote fetch="https://special.googlesource.com/" name="special" \
76 review="https://special.googlesource.com/"/>
78 <default remote="cros" revision="refs/heads/master" sync-j="8"/>
80 <project name="chromeos/manifest-internal" path="manifest-internal" \
81 remote="cros-internal" revision="fe72f0912776fa4596505e236e39286fb217961b" \
82 upstream="refs/heads/master"/>
83 <project groups="minilayout,buildtools" name="chromiumos/chromite" \
84 path="chromite" revision="%(chromite_revision)s" \
85 upstream="refs/heads/master"/>
86 <project name="chromiumos/manifest" path="manifest" \
87 revision="f24b69176b16bf9153f53883c0cc752df8e07d8b" \
88 upstream="refs/heads/master"/>
89 <project groups="minilayout" name="chromiumos/overlays/chromiumos-overlay" \
90 path="src/third_party/chromiumos-overlay" \
91 revision="3ac713c65b5d18585e606a0ee740385c8ec83e44" \
92 upstream="refs/heads/master"/>
93 <project name="chromiumos/special" path="src/special-new" \
94 revision="%(special_revision1)s" \
95 upstream="new-special-branch"/>
96 <project name="chromiumos/special" path="src/special-old" \
97 revision="%(special_revision2)s" \
98 upstream="old-special-branch"/>
99 </manifest>""" % dict(chromite_revision=CHROMITE_REVISION,
100 special_revision1=SPECIAL_REVISION1,
101 special_revision2=SPECIAL_REVISION2)
104 # pylint: disable=W0212,R0901
105 class BranchUtilStageTest(generic_stages_unittest.AbstractStageTest,
106 cros_test_lib.LoggingTestCase):
107 """Tests for branch creation/deletion."""
109 BOT_ID = constants.BRANCH_UTIL_CONFIG
110 DEFAULT_VERSION = '111.0.0'
111 RELEASE_BRANCH_NAME = 'release-test-branch'
113 def _CreateVersionFile(self, version=None):
115 version = self.DEFAULT_VERSION
116 version_file = os.path.join(self.build_root, constants.VERSION_FILE)
117 manifest_version_unittest.VersionInfoTest.WriteFakeVersionFile(
118 version_file, version=version)
121 """Setup patchers for specified bot id."""
122 # Mock out methods as needed.
123 self.StartPatcher(parallel_unittest.ParallelMock())
124 self.StartPatcher(git_unittest.ManifestCheckoutMock())
125 self._CreateVersionFile()
126 self.rc_mock = self.StartPatcher(cros_build_lib_unittest.RunCommandMock())
127 self.rc_mock.SetDefaultCmdResult()
129 # We have a versioned manifest (generated by ManifestVersionSyncStage) and
130 # the regular, user-maintained manifests.
132 '.repo/manifest.xml': VERSIONED_MANIFEST_CONTENTS,
133 'manifest/default.xml': MANIFEST_CONTENTS,
134 'manifest-internal/official.xml': MANIFEST_CONTENTS,
136 for m_path, m_content in manifests.iteritems():
137 full_path = os.path.join(self.build_root, m_path)
138 osutils.SafeMakedirs(os.path.dirname(full_path))
139 osutils.WriteFile(full_path, m_content)
141 self.norm_name = git.NormalizeRef(self.RELEASE_BRANCH_NAME)
143 def _Prepare(self, bot_id=None, **kwargs):
144 if 'cmd_args' not in kwargs:
145 # Fill in cmd_args so we do not use the default, which specifies
146 # --branch. That is incompatible with some branch-util flows.
147 kwargs['cmd_args'] = ['-r', self.build_root]
148 super(BranchUtilStageTest, self)._Prepare(bot_id, **kwargs)
150 def ConstructStage(self):
151 return branch_stages.BranchUtilStage(self._run)
153 def _VerifyPush(self, new_branch, rename_from=None, delete=False):
154 """Verify that |new_branch| has been created.
157 new_branch: The new remote branch to create (or delete).
158 rename_from: If set, |rename_from| is being renamed to |new_branch|.
159 delete: If set, |new_branch| is being deleted.
161 # Pushes all operate on remote branch refs.
162 new_branch = git.NormalizeRef(new_branch)
164 # Calculate source and destination revisions.
165 suffixes = ['', '-new-special-branch', '-old-special-branch']
167 src_revs = [''] * len(suffixes)
168 elif rename_from is not None:
169 rename_from = git.NormalizeRef(rename_from)
170 rename_from_tracking = git.NormalizeRemoteRef('cros', rename_from)
172 '%s%s' % (rename_from_tracking, suffix) for suffix in suffixes
175 src_revs = [CHROMITE_REVISION, SPECIAL_REVISION1, SPECIAL_REVISION2]
176 dest_revs = ['%s%s' % (new_branch, suffix) for suffix in suffixes]
178 # Verify pushes happened correctly.
179 for src_rev, dest_rev in zip(src_revs, dest_revs):
180 cmd = ['push', '%s:%s' % (src_rev, dest_rev)]
181 self.rc_mock.assertCommandContains(cmd)
182 if rename_from is not None:
183 cmd = ['push', ':%s' % (rename_from,)]
184 self.rc_mock.assertCommandContains(cmd)
186 def testRelease(self):
187 """Run-through of branch creation."""
188 self._Prepare(extra_cmd_args=['--branch-name', self.RELEASE_BRANCH_NAME,
189 '--version', self.DEFAULT_VERSION])
190 # Simulate branch not existing.
191 self.rc_mock.AddCmdResult(
192 partial_mock.ListRegex('git show-ref .*%s' % self.RELEASE_BRANCH_NAME),
194 # SHA1 of HEAD for pinned branches.
195 self.rc_mock.AddCmdResult(
196 partial_mock.ListRegex('git rev-parse HEAD'),
199 before = manifest_version.VersionInfo.from_repo(self.build_root)
201 after = manifest_version.VersionInfo.from_repo(self.build_root)
202 # Verify Chrome version was bumped.
203 self.assertEquals(int(after.chrome_branch) - int(before.chrome_branch), 1)
204 self.assertEquals(int(after.build_number) - int(before.build_number), 1)
206 # Verify that manifests were branched properly. Notice that external
207 # is pinned to a SHA1, not an actual branch.
209 'chromite': self.norm_name,
211 'src/special-new': self.norm_name + '-new-special-branch',
212 'src/special-old': self.norm_name + '-old-special-branch',
213 'unpinned': 'refs/heads/master',
215 for m in ['manifest/default.xml', 'manifest-internal/official.xml']:
216 manifest = git.Manifest(os.path.join(self.build_root, m))
217 for project_data in manifest.checkouts_by_path.itervalues():
218 branch_name = branch_names[project_data['path']]
220 'Branch name for %s should be %r, but got %r' %
221 (project_data['path'], branch_name, project_data['revision'])
223 self.assertEquals(project_data['revision'], branch_name, msg)
225 self._VerifyPush(self.norm_name)
227 def testNonRelease(self):
228 """Non-release branch creation."""
229 self._Prepare(extra_cmd_args=['--branch-name', 'refs/heads/test-branch',
230 '--version', self.DEFAULT_VERSION])
231 # Simulate branch not existing.
232 self.rc_mock.AddCmdResult(
233 partial_mock.ListRegex('git show-ref .*test-branch'),
236 before = manifest_version.VersionInfo.from_repo(self.build_root)
237 # Disable the new branch increment so that
238 # IncrementVersionOnDiskForSourceBranch detects we need to bump the version.
239 self.PatchObject(branch_stages.BranchUtilStage,
240 '_IncrementVersionOnDiskForNewBranch', autospec=True)
242 after = manifest_version.VersionInfo.from_repo(self.build_root)
243 # Verify only branch number is bumped.
244 self.assertEquals(after.chrome_branch, before.chrome_branch)
245 self.assertEquals(int(after.build_number) - int(before.build_number), 1)
246 self._VerifyPush(self._run.options.branch_name)
248 def testDeletion(self):
249 """Branch deletion."""
250 self._Prepare(extra_cmd_args=['--branch-name', self.RELEASE_BRANCH_NAME,
252 self.rc_mock.AddCmdResult(
253 partial_mock.ListRegex('git show-ref .*release-test-branch.*'),
254 output='SomeSHA1Value'
257 self._VerifyPush(self.norm_name, delete=True)
259 def testRename(self):
261 self._Prepare(extra_cmd_args=['--branch-name', self.RELEASE_BRANCH_NAME,
262 '--rename-to', 'refs/heads/release-rename'])
263 # Simulate source branch existing and destination branch not existing.
264 self.rc_mock.AddCmdResult(
265 partial_mock.ListRegex('git show-ref .*%s' % self.RELEASE_BRANCH_NAME),
266 output='SomeSHA1Value')
267 self.rc_mock.AddCmdResult(
268 partial_mock.ListRegex('git show-ref .*release-rename'),
271 self._VerifyPush(self._run.options.rename_to, rename_from=self.norm_name)
273 def testDryRun(self):
274 """Verify all pushes are done with --dryrun when --debug is set."""
275 def VerifyDryRun(cmd, *_args, **_kwargs):
276 self.assertTrue('--dry-run' in cmd)
278 # Simulate branch not existing.
279 self.rc_mock.AddCmdResult(
280 partial_mock.ListRegex('git show-ref .*%s' % self.RELEASE_BRANCH_NAME),
283 self._Prepare(extra_cmd_args=['--branch-name', self.RELEASE_BRANCH_NAME,
285 '--version', self.DEFAULT_VERSION])
286 self.rc_mock.AddCmdResult(partial_mock.In('push'),
287 side_effect=VerifyDryRun)
289 self.rc_mock.assertCommandContains(['push', '--dry-run'])
291 def _DetermineIncrForVersion(self, version):
292 version_info = manifest_version.VersionInfo(version)
293 stage_cls = branch_stages.BranchUtilStage
294 return stage_cls.DetermineBranchIncrParams(version_info)
296 def testDetermineIncrBranch(self):
297 """Verify branch increment detection."""
298 incr_type, _ = self._DetermineIncrForVersion(self.DEFAULT_VERSION)
299 self.assertEquals(incr_type, 'branch')
301 def testDetermineIncrPatch(self):
302 """Verify patch increment detection."""
303 incr_type, _ = self._DetermineIncrForVersion('111.1.0')
304 self.assertEquals(incr_type, 'patch')
306 def testDetermineBranchIncrError(self):
307 """Detect unbranchable version."""
308 self.assertRaises(branch_stages.BranchError, self._DetermineIncrForVersion,
311 def _SimulateIncrementFailure(self):
312 """Simulates a git push failure during source branch increment."""
313 self._Prepare(extra_cmd_args=['--branch-name', self.RELEASE_BRANCH_NAME,
314 '--version', self.DEFAULT_VERSION])
315 overlay_dir = os.path.join(
316 self.build_root, constants.CHROMIUMOS_OVERLAY_DIR)
317 self.rc_mock.AddCmdResult(partial_mock.In('push'), returncode=128)
318 stage = self.ConstructStage()
319 args = (overlay_dir, 'gerrit', 'refs/heads/master')
320 stage._IncrementVersionOnDiskForSourceBranch(*args)
322 def testSourceIncrementWarning(self):
323 """Test the warning case for incrementing failure."""
324 # Since all git commands are mocked out, the _FetchAndCheckoutTo function
325 # does nothing, and leaves the chromeos_version.sh file in the bumped state,
326 # so it looks like TOT version was indeed bumped by another bot.
327 with cros_test_lib.LoggingCapturer() as logger:
328 self._SimulateIncrementFailure()
329 self.AssertLogsContain(logger, 'bumped by another')
331 def testSourceIncrementFailure(self):
332 """Test the failure case for incrementing failure."""
333 def FetchAndCheckoutTo(*_args, **_kwargs):
334 self._CreateVersionFile()
336 # Simulate a git checkout of TOT.
337 self.PatchObject(branch_stages.BranchUtilStage, '_FetchAndCheckoutTo',
338 side_effect=FetchAndCheckoutTo, autospec=True)
339 self.assertRaises(cros_build_lib.RunCommandError,
340 self._SimulateIncrementFailure)
343 if __name__ == '__main__':