| # Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Unittests for GerritHelper.""" |
| |
| from __future__ import print_function |
| |
| import getpass |
| import httplib |
| import os |
| import mock |
| |
| from chromite.cbuildbot import config_lib |
| from chromite.cbuildbot import constants |
| from chromite.cbuildbot import validation_pool |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_test_lib |
| from chromite.lib import gerrit |
| from chromite.lib import git |
| from chromite.lib import gob_util |
| from chromite.lib import osutils |
| from chromite.lib import retry_util |
| from chromite.lib import timeout_util |
| |
| |
| site_config = config_lib.GetConfig() |
| |
| |
| # NOTE: The following test cases are designed to run as part of the release |
| # qualification process for the googlesource.com servers: |
| # GerritHelperTest |
| # Any new test cases must be manually added to the qualification test suite. |
| |
| |
| # pylint: disable=W0212,R0904 |
| @cros_test_lib.NetworkTest() |
| class GerritHelperTest(cros_test_lib.GerritTestCase): |
| """Unittests for GerritHelper.""" |
| |
| def _GetHelper(self, remote=site_config.params.EXTERNAL_REMOTE): |
| return gerrit.GetGerritHelper(remote) |
| |
| def createPatch(self, clone_path, project, **kwargs): |
| """Create a patch in the given git checkout and upload it to gerrit. |
| |
| Args: |
| clone_path: The directory on disk of the git clone. |
| project: The associated project. |
| **kwargs: Additional keyword arguments to pass to createCommit. |
| |
| Returns: |
| A GerritPatch object. |
| """ |
| (revision, changeid) = self.createCommit(clone_path, **kwargs) |
| self.uploadChange(clone_path) |
| def PatchQuery(): |
| return self._GetHelper().QuerySingleRecord( |
| change=changeid, project=project, branch='master') |
| # 'RetryException' is needed because there is a race condition between |
| # uploading the change and querying for the change. |
| gpatch = retry_util.RetryException( |
| gerrit.QueryHasNoResults, |
| 5, |
| PatchQuery, |
| sleep=1) |
| self.assertEqual(gpatch.change_id, changeid) |
| self.assertEqual(gpatch.revision, revision) |
| return gpatch |
| |
| def test001SimpleQuery(self): |
| """Create one independent and three dependent changes, then query them.""" |
| project = self.createProject('test001') |
| clone_path = self.cloneProject(project) |
| (head_sha1, head_changeid) = self.createCommit(clone_path) |
| for idx in xrange(3): |
| cros_build_lib.RunCommand( |
| ['git', 'checkout', head_sha1], cwd=clone_path, quiet=True) |
| self.createCommit(clone_path, filename='test-file-%d.txt' % idx) |
| self.uploadChange(clone_path) |
| helper = self._GetHelper() |
| changes = helper.Query(owner='self', project=project) |
| self.assertEqual(len(changes), 4) |
| changes = helper.Query(head_changeid, project=project, branch='master') |
| self.assertEqual(len(changes), 1) |
| self.assertEqual(changes[0].change_id, head_changeid) |
| self.assertEqual(changes[0].sha1, head_sha1) |
| change = helper.QuerySingleRecord( |
| head_changeid, project=project, branch='master') |
| self.assertTrue(change) |
| self.assertEqual(change.change_id, head_changeid) |
| self.assertEqual(change.sha1, head_sha1) |
| change = helper.GrabPatchFromGerrit(project, head_changeid, head_sha1) |
| self.assertTrue(change) |
| self.assertEqual(change.change_id, head_changeid) |
| self.assertEqual(change.sha1, head_sha1) |
| |
| @mock.patch.object(gerrit.GerritHelper, '_GERRIT_MAX_QUERY_RETURN', 2) |
| def test002GerritQueryTruncation(self): |
| """Verify that we detect gerrit truncating our query, and handle it.""" |
| project = self.createProject('test002') |
| clone_path = self.cloneProject(project) |
| # Using a shell loop is markedly faster than running a python loop. |
| num_changes = 5 |
| cmd = ('for ((i=0; i<%i; i=i+1)); do ' |
| 'echo "Another day, another dollar." > test-file-$i.txt; ' |
| 'git add test-file-$i.txt; ' |
| 'git commit -m "Test commit $i."; ' |
| 'done' % num_changes) |
| cros_build_lib.RunCommand(cmd, shell=True, cwd=clone_path, quiet=True) |
| self.uploadChange(clone_path) |
| helper = self._GetHelper() |
| changes = helper.Query(project=project) |
| self.assertEqual(len(changes), num_changes) |
| |
| def test003IsChangeCommitted(self): |
| """Tests that we can parse a json to check if a change is committed.""" |
| project = self.createProject('test003') |
| clone_path = self.cloneProject(project) |
| gpatch = self.createPatch(clone_path, project) |
| helper = self._GetHelper() |
| helper.SetReview(gpatch.gerrit_number, labels={'Code-Review':'+2'}) |
| helper.SubmitChange(gpatch) |
| self.assertTrue(helper.IsChangeCommitted(gpatch.gerrit_number)) |
| |
| gpatch = self.createPatch(clone_path, project) |
| self.assertFalse(helper.IsChangeCommitted(gpatch.gerrit_number)) |
| |
| def test004GetLatestSHA1ForBranch(self): |
| """Verifies that we can query the tip-of-tree commit in a git repository.""" |
| project = self.createProject('test004') |
| clone_path = self.cloneProject(project) |
| for _ in xrange(5): |
| (master_sha1, _) = self.createCommit(clone_path) |
| self.pushBranch(clone_path, 'master') |
| for _ in xrange(5): |
| (testbranch_sha1, _) = self.createCommit(clone_path) |
| self.pushBranch(clone_path, 'testbranch') |
| helper = self._GetHelper() |
| self.assertEqual( |
| helper.GetLatestSHA1ForBranch(project, 'master'), |
| master_sha1) |
| self.assertEqual( |
| helper.GetLatestSHA1ForBranch(project, 'testbranch'), |
| testbranch_sha1) |
| |
| def _ChooseReviewers(self): |
| all_reviewers = set(['[email protected]', '[email protected]', |
| '[email protected]']) |
| ret = list(all_reviewers.difference(['%[email protected]' % getpass.getuser()])) |
| self.assertGreaterEqual(len(ret), 2) |
| return ret |
| |
| def test005SetReviewers(self): |
| """Verify that we can set reviewers on a CL.""" |
| project = self.createProject('test005') |
| clone_path = self.cloneProject(project) |
| gpatch = self.createPatch(clone_path, project) |
| emails = self._ChooseReviewers() |
| helper = self._GetHelper() |
| helper.SetReviewers(gpatch.gerrit_number, add=( |
| emails[0], emails[1])) |
| reviewers = gob_util.GetReviewers(helper.host, gpatch.gerrit_number) |
| self.assertEqual(len(reviewers), 2) |
| self.assertItemsEqual( |
| [r['email'] for r in reviewers], |
| [emails[0], emails[1]]) |
| helper.SetReviewers(gpatch.gerrit_number, |
| remove=(emails[0],)) |
| reviewers = gob_util.GetReviewers(helper.host, gpatch.gerrit_number) |
| self.assertEqual(len(reviewers), 1) |
| self.assertEqual(reviewers[0]['email'], emails[1]) |
| |
| def test006PatchNotFound(self): |
| """Test case where ChangeID isn't found on the server.""" |
| changeids = ['I' + ('deadbeef' * 5), 'I' + ('beadface' * 5)] |
| self.assertRaises(gerrit.GerritException, gerrit.GetGerritPatchInfo, |
| changeids) |
| self.assertRaises(gerrit.GerritException, gerrit.GetGerritPatchInfo, |
| ['*' + cid for cid in changeids]) |
| # Change ID sequence starts at 1000. |
| gerrit_numbers = ['123', '543'] |
| self.assertRaises(gerrit.GerritException, gerrit.GetGerritPatchInfo, |
| gerrit_numbers) |
| self.assertRaises(gerrit.GerritException, gerrit.GetGerritPatchInfo, |
| ['*' + num for num in gerrit_numbers]) |
| |
| def test007VagueQuery(self): |
| """Verify GerritHelper complains if an ID matches multiple changes.""" |
| project = self.createProject('test007') |
| clone_path = self.cloneProject(project) |
| (sha1, _) = self.createCommit(clone_path) |
| (_, changeid) = self.createCommit(clone_path) |
| self.uploadChange(clone_path, 'master') |
| cros_build_lib.RunCommand( |
| ['git', 'checkout', sha1], cwd=clone_path, quiet=True) |
| self.createCommit(clone_path) |
| self.pushBranch(clone_path, 'testbranch') |
| self.createCommit( |
| clone_path, msg='Test commit.\n\nChange-Id: %s' % changeid) |
| self.uploadChange(clone_path, 'testbranch') |
| self.assertRaises(gerrit.GerritException, gerrit.GetGerritPatchInfo, |
| [changeid]) |
| |
| def test008Queries(self): |
| """Verify assorted query operations.""" |
| project = self.createProject('test008') |
| clone_path = self.cloneProject(project) |
| gpatch = self.createPatch(clone_path, project) |
| helper = self._GetHelper() |
| |
| # Multi-queries with one valid and one invalid term should raise. |
| invalid_change_id = 'I1234567890123456789012345678901234567890' |
| self.assertRaises(gerrit.GerritException, gerrit.GetGerritPatchInfo, |
| [invalid_change_id, gpatch.change_id]) |
| self.assertRaises(gerrit.GerritException, gerrit.GetGerritPatchInfo, |
| [gpatch.change_id, invalid_change_id]) |
| self.assertRaises(gerrit.GerritException, gerrit.GetGerritPatchInfo, |
| ['9876543', gpatch.gerrit_number]) |
| self.assertRaises(gerrit.GerritException, gerrit.GetGerritPatchInfo, |
| [gpatch.gerrit_number, '9876543']) |
| |
| # Simple query by project/changeid/sha1. |
| patch_info = helper.GrabPatchFromGerrit(gpatch.project, gpatch.change_id, |
| gpatch.sha1) |
| self.assertEqual(patch_info.gerrit_number, gpatch.gerrit_number) |
| self.assertEqual(patch_info.remote, site_config.params.EXTERNAL_REMOTE) |
| |
| # Simple query by gerrit number to external remote. |
| patch_info = gerrit.GetGerritPatchInfo([gpatch.gerrit_number]) |
| self.assertEqual(patch_info[0].gerrit_number, gpatch.gerrit_number) |
| self.assertEqual(patch_info[0].remote, site_config.params.EXTERNAL_REMOTE) |
| |
| # Simple query by gerrit number to internal remote. |
| patch_info = gerrit.GetGerritPatchInfo(['*' + gpatch.gerrit_number]) |
| self.assertEqual(patch_info[0].gerrit_number, gpatch.gerrit_number) |
| self.assertEqual(patch_info[0].remote, site_config.params.INTERNAL_REMOTE) |
| |
| # Query to external server by gerrit number and change-id which refer to |
| # the same change should return one result. |
| fq_changeid = '~'.join((gpatch.project, 'master', gpatch.change_id)) |
| patch_info = gerrit.GetGerritPatchInfo([gpatch.gerrit_number, fq_changeid]) |
| self.assertEqual(len(patch_info), 1) |
| self.assertEqual(patch_info[0].gerrit_number, gpatch.gerrit_number) |
| self.assertEqual(patch_info[0].remote, site_config.params.EXTERNAL_REMOTE) |
| |
| # Query to internal server by gerrit number and change-id which refer to |
| # the same change should return one result. |
| patch_info = gerrit.GetGerritPatchInfo( |
| ['*' + gpatch.gerrit_number, '*' + fq_changeid]) |
| self.assertEqual(len(patch_info), 1) |
| self.assertEqual(patch_info[0].gerrit_number, gpatch.gerrit_number) |
| self.assertEqual(patch_info[0].remote, site_config.params.INTERNAL_REMOTE) |
| |
| def test009SubmitOutdatedCommit(self): |
| """Tests that we can parse a json to check if a change is committed.""" |
| project = self.createProject('test009') |
| clone_path = self.cloneProject(project, 'p1') |
| |
| # Create a change. |
| gpatch1 = self.createPatch(clone_path, project) |
| |
| # Update the change. |
| gpatch2 = self.createPatch(clone_path, project, amend=True) |
| |
| # Make sure we're talking about the same change. |
| self.assertEqual(gpatch1.change_id, gpatch2.change_id) |
| |
| # Try submitting the out-of-date change. |
| helper = self._GetHelper() |
| helper.SetReview(gpatch1.gerrit_number, labels={'Code-Review':'+2'}) |
| with self.assertRaises(gob_util.GOBError) as ex: |
| helper.SubmitChange(gpatch1) |
| self.assertEqual(ex.exception.http_status, httplib.CONFLICT) |
| self.assertFalse(helper.IsChangeCommitted(gpatch1.gerrit_number)) |
| |
| # Try submitting the up-to-date change. |
| helper.SubmitChange(gpatch2) |
| helper.IsChangeCommitted(gpatch2.gerrit_number) |
| |
| def test010SubmitBatchUsingGit(self): |
| project = self.createProject('test012') |
| |
| helper = self._GetHelper() |
| repo = self.cloneProject(project, 'p1') |
| initial_patch = self.createPatch(repo, project, msg='Init') |
| helper.SetReview(initial_patch.gerrit_number, labels={'Code-Review':'+2'}) |
| helper.SubmitChange(initial_patch) |
| # GoB does not guarantee that the change will be in "merged" state |
| # atomically after the /Submit endpoint is called. |
| timeout_util.WaitForReturnTrue( |
| lambda: helper.IsChangeCommitted(initial_patch.gerrit_number), |
| timeout=60) |
| |
| patchA = self.createPatch(repo, project, |
| msg='Change A', |
| filename='a.txt') |
| |
| osutils.WriteFile(os.path.join(repo, 'aoeu.txt'), 'asdf') |
| git.RunGit(repo, ['add', 'aoeu.txt']) |
| git.RunGit(repo, ['commit', '--amend', '--reuse-message=HEAD']) |
| sha1 = git.RunGit(repo, |
| ['rev-list', '-n1', 'HEAD']).output.strip() |
| |
| patchA.sha1 = sha1 |
| patchA.revision = sha1 |
| |
| patchB = self.createPatch(repo, project, |
| msg='Change B', |
| filename='b.txt') |
| |
| pool = validation_pool.ValidationPool( |
| overlays=constants.PUBLIC, |
| build_root='', |
| build_number=0, |
| builder_name='', |
| is_master=False, |
| dryrun=False) |
| |
| by_repo = {repo: [patchA, patchB]} |
| pool.SubmitLocalChanges( |
| by_repo, |
| reason="Testing submitting changes in batch via Git.") |
| |
| self.assertTrue(helper.IsChangeCommitted(patchB.gerrit_number)) |
| self.assertTrue(helper.IsChangeCommitted(patchA.gerrit_number)) |
| for patch in [patchA, patchB]: |
| self.assertTrue(helper.IsChangeCommitted(patch.gerrit_number)) |
| |
| def test011ResetReviewLabels(self): |
| """Tests that we can remove a code review label.""" |
| project = self.createProject('test011') |
| helper = self._GetHelper() |
| clone_path = self.cloneProject(project, 'p1') |
| gpatch = self.createPatch(clone_path, project, msg='Init') |
| helper.SetReview(gpatch.gerrit_number, labels={'Code-Review':'+2'}) |
| gob_util.ResetReviewLabels(helper.host, gpatch.gerrit_number, |
| label='Code-Review', notify='OWNER') |
| |
| def test012ApprovalTime(self): |
| """Approval timestamp should be reset when a new patchset is created.""" |
| # Create a change. |
| project = self.createProject('test013') |
| helper = self._GetHelper() |
| clone_path = self.cloneProject(project, 'p1') |
| gpatch = self.createPatch(clone_path, project, msg='Init') |
| helper.SetReview(gpatch.gerrit_number, labels={'Code-Review':'+2'}) |
| |
| # Update the change. |
| new_msg = 'New %s' % gpatch.commit_message |
| cros_build_lib.RunCommand( |
| ['git', 'commit', '--amend', '-m', new_msg], cwd=clone_path, quiet=True) |
| self.uploadChange(clone_path) |
| gpatch2 = self._GetHelper().QuerySingleRecord( |
| change=gpatch.change_id, project=gpatch.project, branch='master') |
| self.assertNotEqual(gpatch2.approval_timestamp, 0) |
| self.assertNotEqual(gpatch2.commit_timestamp, 0) |
| self.assertEqual(gpatch2.approval_timestamp, gpatch2.commit_timestamp) |
| |
| |
| @cros_test_lib.NetworkTest() |
| class DirectGerritHelperTest(cros_test_lib.TestCase): |
| """Unittests for GerritHelper that use the real Chromium instance.""" |
| |
| # A big list of real changes. |
| CHANGES = ['235893', '*189165', '231790', '*190026', '231647', '234645'] |
| |
| def testMultipleChangeDetail(self): |
| """Test ordering of results in GetMultipleChangeDetail""" |
| changes = [x for x in self.CHANGES if not x.startswith('*')] |
| helper = gerrit.GetCrosExternal() |
| results = list(helper.GetMultipleChangeDetail([str(x) for x in changes])) |
| gerrit_numbers = [str(x['_number']) for x in results] |
| self.assertEqual(changes, gerrit_numbers) |
| |
| def testQueryMultipleCurrentPatchset(self): |
| """Test ordering of results in QueryMultipleCurrentPatchset""" |
| changes = [x for x in self.CHANGES if not x.startswith('*')] |
| helper = gerrit.GetCrosExternal() |
| results = list(helper.QueryMultipleCurrentPatchset(changes)) |
| self.assertEqual(changes, [x.gerrit_number for _, x in results]) |
| self.assertEqual(changes, [x for x, _ in results]) |
| |
| def testGetGerritPatchInfo(self): |
| """Test ordering of results in GetGerritPatchInfo""" |
| changes = self.CHANGES |
| results = list(gerrit.GetGerritPatchInfo(changes)) |
| self.assertEqual(changes, [x.gerrit_number_str for x in results]) |