def NotifyCulprit(culprit, bug_id): """Sends a notification to a code review page. Args: culprit (FlakeCulprit): The culprit identified to have introduced flakiness. bud_id (int): An optional bug id to include in the notification. Pass None if not specified. Returns: Bool indicating whether a notification was sent. """ assert culprit revision = culprit.revision culprit_info = git.GetCodeReviewInfoForACommit(revision) review_server_host = culprit_info.get('review_server_host') review_change_id = culprit_info.get('review_change_id') code_review_settings = waterfall_config.GetCodeReviewSettings() codereview = Gerrit( review_server_host ) if review_server_host and codereview_util.IsCodeReviewGerrit( review_server_host, code_review_settings) else None sent = False if codereview and review_change_id: message = _GenerateMessageText(culprit, bug_id) sent = codereview.PostMessage(review_change_id, message) else: # Occasionally, a commit was not uploaded for code-review. logging.error('No code-review url for %s/%s', culprit.repo_name, culprit.revision) status = analysis_status.COMPLETED if sent else analysis_status.ERROR suspected_cl_util.UpdateCulpritNotificationStatus(culprit.key.urlsafe(), status) return sent
class GerritTest(testing.AppengineTestCase): def setUp(self): super(GerritTest, self).setUp() self.http_client = DummyHttpClient() self.server_hostname = 'server.host.name' self.gerrit = Gerrit(self.server_hostname) self.gerrit.HTTP_CLIENT = self.http_client self.maxDiff = None def testGetCodeReviewUrl(self): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' self.assertEqual( 'https://server.host.name/q/%s' % change_id, self.gerrit.GetCodeReviewUrl(change_id)) def testPostMessage(self): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' response_str = '{}' self.http_client._SetPostMessageResponse( self.server_hostname, change_id, response_str) # This message should not change when being urlencoded or jsonized message = 'FinditWasHere' self.assertTrue(self.gerrit.PostMessage(change_id, message)) _url, data, _headers = self.http_client.requests[0] self.assertIn(message, data) def testAddReviewerNew(self): change_id = 'Iabc12345' self.http_client._SetReviewersResponse(self.server_hostname, change_id, ['*****@*****.**']) self.http_client._AddReviewerResponse(self.server_hostname, change_id, '*****@*****.**') self.assertTrue(self.gerrit.AddReviewers(change_id, ['*****@*****.**'])) def testAddReviewerNewAliased(self): change_id = 'Iabc12345' self.http_client._SetReviewersResponse(self.server_hostname, change_id, ['*****@*****.**']) self.http_client._AddReviewerResponse(self.server_hostname, change_id, '*****@*****.**') self.assertTrue(self.gerrit.AddReviewers(change_id, ['*****@*****.**'])) def testAddReviewerExisting(self): change_id = 'Iabc12345' self.http_client._SetReviewersResponse(self.server_hostname, change_id, ['*****@*****.**']) self.assertTrue(self.gerrit.AddReviewers(change_id, ['*****@*****.**'])) def testAddReviewerExistingAliased(self): change_id = 'Iabc12345' self.http_client._SetReviewersResponse(self.server_hostname, change_id, ['*****@*****.**']) self.http_client._AddReviewerResponse(self.server_hostname, change_id, None) self.assertTrue(self.gerrit.AddReviewers(change_id, ['*****@*****.**'])) def testAddReviewerMany(self): change_id = 'Iabc12345' self.http_client._SetReviewersResponse(self.server_hostname, change_id, ['*****@*****.**']) self.http_client._AddReviewerResponse(self.server_hostname, change_id, '*****@*****.**') self.http_client._AddReviewerResponse(self.server_hostname, change_id, '*****@*****.**') self.assertTrue(self.gerrit.AddReviewers(change_id, ['*****@*****.**', '*****@*****.**', '*****@*****.**'])) def testAddReviewerFailure(self): change_id = 'Iabc12345' self.http_client._SetReviewersResponse(self.server_hostname, change_id, ['*****@*****.**']) self.assertFalse(self.gerrit.AddReviewers(change_id, ['*****@*****.**'])) def testGetClInfoCQCommit(self): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' with mock.patch.object( self.gerrit, '_Get', return_value={ 'change_id': 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7', 'status': 'MERGED', 'owner': { 'email': '*****@*****.**' }, 'submitted': '2017-03-24 01:07:39.000000000', 'reviewers': { 'REVIEWER': [ {'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'} ], 'CC': [{'email': '*****@*****.**'}] }, 'messages': [{ 'author': {'email': '*****@*****.**'}, 'date': '2017-03-24 00:58:25.000000000', 'message': 'Patch Set 3: Commit-Queue+2', '_revision_number': 3 }], 'current_revision': '4bd07b5148508d3d100d9d4eafb9f4d233d7fa15', 'revisions': { '4bd07b5148508d3d100d9d4eafb9f4d233d7fa15': { '_number': 3, 'commit': { 'committer': { 'email': '*****@*****.**' }, 'message': 'some message' } } } }): cl_info = self.gerrit.GetClDetails(change_id) self.assertNotEqual( cl_info.commit_attempts.values()[0].last_cq_timestamp, cl_info.commits[0].timestamp) def testGetClInfoManualCommit(self): change_id = 'I7ecd56d7d0c3fef90cfe998a29b948c5032980e4' with mock.patch.object( self.gerrit, '_Get', return_value={ 'change_id': 'I7ecd56d7d0c3fef90cfe998a29b948c5032980e4', 'status': 'MERGED', 'owner': { 'email': '*****@*****.**' }, 'submitted': '2017-03-08 04:43:30.000000000', 'reviewers': { 'REVIEWER': [ {'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**' }, ], 'CC': [] }, 'messages': [], 'current_revision': '9ff179a3962bf3465815d3a85ad46e1c3b4a9e27', 'revisions': { '9ff179a3962bf3465815d3a85ad46e1c3b4a9e27': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', }, 'message': 'some message' } } } }): cl_info = self.gerrit.GetClDetails(change_id) self.assertEqual( cl_info.commit_attempts.values()[0].last_cq_timestamp, cl_info.commits[0].timestamp) def testGetClInfoRevertedCommit(self): change_id = 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0' with mock.patch.object( self.gerrit, '_Get', side_effect=[{ 'change_id': 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0', 'status': 'MERGED', 'owner': { 'email': '*****@*****.**' }, 'submitted': '2017-02-27 18:56:54.000000000', '_number': 446905, 'reviewers': { 'REVIEWER': [ {'email': '*****@*****.**'}, {'email': '*****@*****.**'}, {'email': '*****@*****.**'} ], 'CC': [] }, 'messages': [ { 'id': 'b7d6785c324297ec4f1e6b2de34cf83f4c58e87c', 'author': {'email': '*****@*****.**'}, 'date': '2017-02-27 18:47:15.000000000', 'message': 'Patch Set 1: Commit-Queue+2', '_revision_number': 1 }, { 'id': 'b9e04aec4bffed0284c1f53cc5a9c88818807368', 'tag': 'autogenerated:gerrit:revert', 'author': {'email': '*****@*****.**'}, 'date': '2017-02-27 19:04:51.000000000', 'message': 'Created a revert of this change as ' 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1', '_revision_number': 2 } ], 'current_revision': 'edda1046ce724695004242e943f59f5e1b2d00ff', 'revisions': { 'edda1046ce724695004242e943f59f5e1b2d00ff': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', }, 'message': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT= True\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\nCommit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' }, } } }, { 'change_id': 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1', 'status': 'MERGED', 'submitted': '2017-02-27 19:05:03.000000000', 'owner': { 'email': '*****@*****.**' }, '_number': 446788, 'reviewers': { 'REVIEWER': [ { 'email': '*****@*****.**', }, { 'email': '*****@*****.**', }, { 'email': '*****@*****.**', } ] }, 'messages': [ { 'id': '30496ce351a43c0b74d812e9e40b440f5acff9d5', 'author': { 'email': '*****@*****.**', }, 'date': '2017-02-27 19:04:53.000000000', 'message': 'Patch Set 1: Code-Review+1 Commit-Queue+2', '_revision_number': 1 }, ], 'current_revision': 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824', 'revisions': { 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', 'date': '2017-02-27 19:05:03.000000000', 'tz': 0 }, 'message': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT=TRUE\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\nCommit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' }, } } }]): cl_info = self.gerrit.GetClDetails(change_id) self.assertEqual(cl_info.serialize(), { 'server_hostname': 'server.host.name', 'auto_revert_off': True, 'owner_email': '*****@*****.**', 'reviewers': ['*****@*****.**', '*****@*****.**', '*****@*****.**'], 'closed': True, 'commits': [{'patchset_id': 2, 'timestamp': '2017-02-27 18:56:54 UTC', 'revision': 'edda1046ce724695004242e943f59f5e1b2d00ff'}], 'cc': [], 'change_id': 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0', 'reverts': [{'patchset_id': 2, 'reverting_user_email': '*****@*****.**', 'timestamp': '2017-02-27 19:04:51 UTC', 'reverting_cl': { 'server_hostname': 'server.host.name', 'auto_revert_off': True, 'owner_email': '*****@*****.**', 'reviewers': ['*****@*****.**', '*****@*****.**', '*****@*****.**'], 'closed': True, 'commits': [{ 'patchset_id': 2, 'timestamp': '2017-02-27 19:05:03 UTC', 'revision': 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824'}], 'cc': [], 'change_id': 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1', 'reverts': [], 'commit_attempts': [{ 'patchset_id': 1, 'timestamp': '2017-02-27 19:04:53 UTC', 'committing_user_email': '*****@*****.**'}]}}], 'commit_attempts': [{'patchset_id': 1, 'timestamp': '2017-02-27 18:47:15 UTC', 'committing_user_email': '*****@*****.**'}]}) def testCreateRevertSuccessful(self): change_id = 'I123456' reverting_change_id = 'I987654' response = self.http_client._MakeResponse( {'change_id': reverting_change_id}) url = 'https://%s/a/changes/%s/revert' % (self.server_hostname, change_id) self.http_client.SetResponse(url, (200, response)) self.assertEqual(reverting_change_id, self.gerrit.CreateRevert( 'Reason', change_id)) def testCreateRevertFailure(self): change_id = 'I123456' self.assertFalse(self.gerrit.CreateRevert('Reason', change_id)) def testRequestAddsAuthenticationPrefix(self): self.gerrit._AuthenticatedRequest(['changes', '123']) url, _payload, _headers = self.http_client.requests[0] self.assertEqual('https://server.host.name/a/changes/123', url) def testRequestKeepsAuthenticationPrefix(self): self.gerrit._AuthenticatedRequest(['a', 'changes', '123']) url, _payload, _headers = self.http_client.requests[0] self.assertEqual('https://server.host.name/a/changes/123', url)
class GerritTest(testing.AppengineTestCase): def setUp(self): super(GerritTest, self).setUp() self.http_client = DummyHttpClient() self.server_hostname = 'server.host.name' self.gerrit = Gerrit(self.server_hostname) self.gerrit.HTTP_CLIENT = self.http_client self.maxDiff = None def testGetCodeReviewUrl(self): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' self.assertEqual('https://server.host.name/q/%s' % change_id, self.gerrit.GetCodeReviewUrl(change_id)) def testPostMessage(self): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' response_str = '{}' self.http_client._SetPostMessageResponse(self.server_hostname, change_id, response_str) # This message should not change when being urlencoded or jsonized message = 'FinditWasHere' self.assertTrue(self.gerrit.PostMessage(change_id, message)) _url, data, _headers = self.http_client.requests[0] self.assertIn(message, data) def testPostMessageNoEmail(self): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' response_str = '{}' self.http_client._SetPostMessageResponse(self.server_hostname, change_id, response_str) # This message should not change when being urlencoded or jsonized message = 'FinditWasHere' self.assertTrue(self.gerrit.PostMessage(change_id, message, False)) _url, data, _headers = self.http_client.requests[0] self.assertIn(message, data) def testAddReviewerNew(self): change_id = 'Iabc12345' response = { 'reviewers': { '*****@*****.**': { 'input': '*****@*****.**', 'reviewers': [{ 'email': '*****@*****.**' }] } } } self.http_client._SetPostMessageResponse(self.server_hostname, change_id, json.dumps(response)) self.assertTrue( self.gerrit.AddReviewers(change_id, ['*****@*****.**'], 'message')) def testAddReviewerWrongformat(self): change_id = 'Iabc12345' self.assertTrue( self.gerrit.AddReviewers(change_id, ['[email protected]@d.com'], 'message')) def testAddReviewerExisting(self): change_id = 'Iabc12345' response = { 'reviewers': { '*****@*****.**': { 'input': '*****@*****.**', 'reviewers': [] } } } self.http_client._SetPostMessageResponse(self.server_hostname, change_id, json.dumps(response)) self.assertTrue( self.gerrit.AddReviewers(change_id, ['*****@*****.**'], 'message')) @mock.patch.object(logging, 'error') def testAddReviewerMany(self, mock_logging): # pylint:disable=unused-argument change_id = 'Iabc12345' response = { 'reviewers': { '*****@*****.**': { 'input': '*****@*****.**', 'reviewers': [{ 'email': '*****@*****.**' }] }, '*****@*****.**': { 'input': '*****@*****.**', 'reviewers': [{ 'email': '*****@*****.**' }] }, '*****@*****.**': { 'input': '*****@*****.**', 'reviewers': [{ 'email': '*****@*****.**' }] }, } } self.http_client._SetPostMessageResponse(self.server_hostname, change_id, json.dumps(response)) self.assertTrue( self.gerrit.AddReviewers(change_id, [ '*****@*****.**', '*****@*****.**', '*****@*****.**', '[email protected]@d.com' ], 'message')) # Assertions have never worked properly because we were using mock 1.0.1. # After rolling to mock 2.0.0, which fixes assertions, these assertions now # fail. https://crbug.com/947753. # pylint:disable=unused-argument # mock_logging.assert_has_called_with( # 'Reviewer\'s email is in wrong format: ' # '[email protected]@d.com') def testAddReviewerFailure(self): change_id = 'Iabc12345' self.assertFalse(self.gerrit.AddReviewers(change_id, ['*****@*****.**'])) def testGetClInfoCQCommit(self): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' with mock.patch.object( self.gerrit, '_Get', return_value={ 'change_id': 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7', 'status': 'MERGED', 'owner': { 'email': '*****@*****.**' }, 'submitted': '2017-03-24 01:07:39.000000000', 'reviewers': { 'REVIEWER': [{ 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }], 'CC': [{ 'email': '*****@*****.**' }] }, 'messages': [{ 'author': { 'email': '*****@*****.**' }, 'date': '2017-03-24 00:58:25.000000000', 'message': 'Patch Set 3: Commit-Queue+2', '_revision_number': 3 }], 'current_revision': '4bd07b5148508d3d100d9d4eafb9f4d233d7fa15', 'revisions': { '4bd07b5148508d3d100d9d4eafb9f4d233d7fa15': { '_number': 3, 'commit': { 'committer': { 'email': '*****@*****.**' }, 'message': 'some message', 'parents': [{ 'commit': ('ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f' ), }] } } }, 'subject': 'subject' }): cl_info = self.gerrit.GetClDetails(change_id) self.assertNotEqual( cl_info.commit_attempts.values()[0].last_cq_timestamp, cl_info.commits[0].timestamp) def testGetClInfoManualCommit(self): change_id = 'I7ecd56d7d0c3fef90cfe998a29b948c5032980e4' with mock.patch.object( self.gerrit, '_Get', return_value={ 'change_id': 'I7ecd56d7d0c3fef90cfe998a29b948c5032980e4', 'status': 'MERGED', 'owner': { 'email': '*****@*****.**' }, 'submitted': '2017-03-08 04:43:30.000000000', 'reviewers': { 'REVIEWER': [ { 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }, ], 'CC': [] }, 'messages': [], 'current_revision': '9ff179a3962bf3465815d3a85ad46e1c3b4a9e27', 'revisions': { '9ff179a3962bf3465815d3a85ad46e1c3b4a9e27': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', }, 'message': 'some message', 'parents': [{ 'commit': ('ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f' ), }] } } }, 'subject': 'subject' }): cl_info = self.gerrit.GetClDetails(change_id) self.assertEqual(cl_info.commit_attempts.values()[0].last_cq_timestamp, cl_info.commits[0].timestamp) def testGetClInfoRevertedCommit(self): change_id = 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0' with mock.patch.object( self.gerrit, '_Get', side_effect=[{ 'change_id': 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0', 'status': 'MERGED', 'owner': { 'email': '*****@*****.**' }, 'submitted': '2017-02-27 18:56:54.000000000', '_number': 446905, 'reviewers': { 'REVIEWER': [{ 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }], 'CC': [] }, 'messages': [{ 'id': 'b7d6785c324297ec4f1e6b2de34cf83f4c58e87c', 'author': { 'email': '*****@*****.**' }, 'date': '2017-02-27 18:47:15.000000000', 'message': 'Patch Set 1: Commit-Queue+2', '_revision_number': 1 }, { 'id': 'b9e04aec4bffed0284c1f53cc5a9c88818807368', 'tag': 'autogenerated:gerrit:revert', 'author': { 'email': '*****@*****.**' }, 'date': '2017-02-27 19:04:51.000000000', 'message': 'Created a revert of this change as ' 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1', '_revision_number': 2 }], 'current_revision': 'edda1046ce724695004242e943f59f5e1b2d00ff', 'revisions': { 'edda1046ce724695004242e943f59f5e1b2d00ff': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', }, 'message': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT= True\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\nCommit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' 'BUGS : 12345, 67890', 'parents': [{ 'commit': ('ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f' ), }] }, } }, 'subject': 'subject' }, { 'change_id': 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1', 'status': 'MERGED', 'submitted': '2017-02-27 19:05:03.000000000', 'owner': { 'email': '*****@*****.**' }, '_number': 446788, 'reviewers': { 'REVIEWER': [{ 'email': '*****@*****.**', }, { 'email': '*****@*****.**', }, { 'email': '*****@*****.**', }] }, 'messages': [ { 'id': '30496ce351a43c0b74d812e9e40b440f5acff9d5', 'author': { 'email': '*****@*****.**', }, 'date': '2017-02-27 19:04:53.000000000', 'message': 'Patch Set 1: Code-Review+1 Commit-Queue+2', '_revision_number': 1 }, ], 'current_revision': 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824', 'revisions': { 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', 'date': '2017-02-27 19:05:03.000000000', 'tz': 0 }, 'message': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT=TRUE\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\nCommit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' 'BUG: 123455', 'parents': [{ 'commit': ('b63383e4e3f2b774ce0e6d3bfafb4adc84e48f2b' ), }] }, } }, 'subject': 'subject', 'revert_of': 446905 }]): cl_info = self.gerrit.GetClDetails(change_id) expected_cl_info = { 'server_hostname': 'server.host.name', 'auto_revert_off': True, 'owner_email': '*****@*****.**', 'reviewers': [ '*****@*****.**', '*****@*****.**', '*****@*****.**' ], 'closed': True, 'commits': [{ 'patchset_id': 2, 'timestamp': '2017-02-27 18:56:54 UTC', 'revision': 'edda1046ce724695004242e943f59f5e1b2d00ff', 'parent_revisions': ['ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f'] }], 'cc': [], 'subject': 'subject', 'description': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT= True\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\nCommit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' 'BUGS : 12345, 67890', 'change_id': 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0', 'reverts': [{ 'patchset_id': 2, 'reverting_user_email': '*****@*****.**', 'timestamp': '2017-02-27 19:04:51 UTC', 'reverting_cl': { 'server_hostname': 'server.host.name', 'auto_revert_off': True, 'owner_email': '*****@*****.**', 'reviewers': [ '*****@*****.**', '*****@*****.**', '*****@*****.**' ], 'closed': True, 'commits': [{ 'patchset_id': 2, 'timestamp': '2017-02-27 19:05:03 UTC', 'revision': 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824', 'parent_revisions': ['b63383e4e3f2b774ce0e6d3bfafb4adc84e48f2b'] }], 'cc': [], 'subject': 'subject', 'description': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT=TRUE\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\n' 'Commit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' 'BUG: 123455', 'change_id': 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1', 'reverts': [], 'commit_attempts': [{ 'patchset_id': 1, 'timestamp': '2017-02-27 19:04:53 UTC', 'committing_user_email': '*****@*****.**' }], 'revert_of': 446905, 'patchsets': { 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824': { 'patchset_id': 2, 'revision': 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824', 'parent_revisions': ['b63383e4e3f2b774ce0e6d3bfafb4adc84e48f2b'] } } } }], 'commit_attempts': [{ 'patchset_id': 1, 'timestamp': '2017-02-27 18:47:15 UTC', 'committing_user_email': '*****@*****.**' }], 'revert_of': None, 'patchsets': { 'edda1046ce724695004242e943f59f5e1b2d00ff': { 'patchset_id': 2, 'revision': 'edda1046ce724695004242e943f59f5e1b2d00ff', 'parent_revisions': ['ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f'] } } } self.assertEqual(cl_info.serialize(), expected_cl_info) def testCreateRevertSuccessful(self): change_id = 'I123456' reverting_change_id = 'I987654' response = self.http_client._MakeResponse( {'change_id': reverting_change_id}) url = 'https://%s/a/changes/%s/revert' % (self.server_hostname, change_id) self.http_client.SetResponse(url, (200, response)) with mock.patch.object(self.gerrit, '_GenerateRevertCLDescription', return_value='Reason'): self.assertEqual(reverting_change_id, self.gerrit.CreateRevert('Reason', change_id)) def testCreateRevertFailure(self): change_id = 'I123456' with mock.patch.object(self.gerrit, '_GenerateRevertCLDescription', return_value='Reason'): self.assertFalse(self.gerrit.CreateRevert('Reason', change_id)) def testRequestAddsAuthenticationPrefix(self): self.gerrit._AuthenticatedRequest(['changes', '123']) url, _payload, _headers = self.http_client.requests[0] self.assertEqual('https://server.host.name/a/changes/123', url) def testRequestKeepsAuthenticationPrefix(self): self.gerrit._AuthenticatedRequest(['a', 'changes', '123']) url, _payload, _headers = self.http_client.requests[0] self.assertEqual('https://server.host.name/a/changes/123', url) @mock.patch.object(time_util, 'GetUTCNow', return_value=datetime.datetime(2017, 2, 27, 20, 0, 0)) def testGenerateRevertCLDescriptionBypassCQ(self, _): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' reason = 'Reason' original_cl_description = textwrap.dedent(""" cl title some description NOAUTOREVERT= True BUGS : 12345, 67890 Cq-Include-Trybots: m1.b1:m2.b2 Change-Id: someid Reviewed-on: cl_url Commit-Queue: owner Reviewed-by: reviewers """).strip() expected_description = textwrap.dedent(""" Revert "cl title" This reverts commit edda1046ce724695004242e943f59f5e1b2d00ff. Reason for revert: Reason Original change's description: > cl title > > some description > > NOAUTOREVERT= True > BUGS : 12345, 67890 > Cq-Include-Trybots: m1.b1:m2.b2 > Change-Id: someid > Reviewed-on: cl_url > Commit-Queue: owner > Reviewed-by: reviewers No-Presubmit: true No-Tree-Checks: true No-Try: true BUGS : 12345, 67890 Cq-Include-Trybots: m1.b1:m2.b2 """).strip() with mock.patch.object( self.gerrit, '_Get', return_value={ 'change_id': 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0', 'status': 'MERGED', 'owner': { 'email': '*****@*****.**' }, 'submitted': '2017-02-27 18:56:54.000000000', '_number': 446905, 'reviewers': { 'REVIEWER': [{ 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }], 'CC': [] }, 'messages': [{ 'id': 'b7d6785c324297ec4f1e6b2de34cf83f4c58e87c', 'author': { 'email': '*****@*****.**' }, 'date': '2017-02-27 18:47:15.000000000', 'message': 'Patch Set 1: Commit-Queue+2', '_revision_number': 1 }], 'current_revision': 'edda1046ce724695004242e943f59f5e1b2d00ff', 'revisions': { 'edda1046ce724695004242e943f59f5e1b2d00ff': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', }, 'message': original_cl_description, 'parents': [{ 'commit': ('ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f' ), }] }, } }, 'subject': 'cl title' }): # Assertions have never worked properly because we were using mock 1.0.1. # After rolling to mock 2.0.0, which fixes assertions, these assertions # now fail. https://crbug.com/948213 # pylint:disable=unused-argument # self.assertEqual( # expected_description, # self.gerrit._GenerateRevertCLDescription(change_id, reason)) pass @mock.patch.object(time_util, 'GetUTCNow', return_value=datetime.datetime(2017, 6, 1, 1, 0, 0)) def testGenerateRevertCLDescription(self, _): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' reason = 'Reason' original_cl_description = textwrap.dedent(""" cl title some description NOAUTOREVERT= True BUGS : 12345, 67890 Cq-Include-Trybots: m1.b1:m2.b2 Change-Id: someid Reviewed-on: cl_url Commit-Queue: owner Reviewed-by: reviewers """).strip() expected_description = textwrap.dedent(""" Revert "cl title" This reverts commit edda1046ce724695004242e943f59f5e1b2d00ff. Reason for revert: Reason Original change's description: > cl title > > some description > > NOAUTOREVERT= True > BUGS : 12345, 67890 > Cq-Include-Trybots: m1.b1:m2.b2 > Change-Id: someid > Reviewed-on: cl_url > Commit-Queue: owner > Reviewed-by: reviewers # Not skipping CQ checks because original CL landed > 1 day ago. BUGS : 12345, 67890 Cq-Include-Trybots: m1.b1:m2.b2 """).strip() with mock.patch.object( self.gerrit, '_Get', return_value={ 'change_id': 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0', 'status': 'MERGED', 'owner': { 'email': '*****@*****.**' }, 'submitted': '2017-02-27 18:56:54.000000000', '_number': 446905, 'reviewers': { 'REVIEWER': [{ 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }], 'CC': [] }, 'messages': [{ 'id': 'b7d6785c324297ec4f1e6b2de34cf83f4c58e87c', 'author': { 'email': '*****@*****.**' }, 'date': '2017-02-27 18:47:15.000000000', 'message': 'Patch Set 1: Commit-Queue+2', '_revision_number': 1 }], 'current_revision': 'edda1046ce724695004242e943f59f5e1b2d00ff', 'revisions': { 'edda1046ce724695004242e943f59f5e1b2d00ff': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', }, 'message': original_cl_description, 'parents': [{ 'commit': ('ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f' ), }] }, } }, 'subject': 'cl title' }): # Assertions have never worked properly because we were using mock 1.0.1. # After rolling to mock 2.0.0, which fixes assertions, these assertions # now fail. https://crbug.com/948213 # pylint:disable=unused-argument # self.assertEqual( # expected_description, # self.gerrit._GenerateRevertCLDescription(change_id, reason)) pass def testGetBugLine(self): expected_results = { 'message': '', 'BUG: 24343\n': 'BUG: 24343\n', 'Bug: 23234\n': 'Bug: 23234\n', 'issue: 34254\n': 'issue: 34254\n' } for k, v in expected_results.iteritems(): self.assertEqual(v, self.gerrit._GetBugLine(k)) self.assertEqual('Bug: 12345\n', self.gerrit._GetBugLine('', '12345')) self.assertEqual('BUG: 12345, 67890\n', self.gerrit._GetBugLine('BUG: 12345\n', '67890')) def testGetCQTryBotLine(self): expected_results = { 'message': '', 'cq-include-trybots: m1.b1:m2.b2\n': 'cq-include-trybots: m1.b1:m2.b2\n', 'CQ_INCLUDE_TRYBOTS= m1.b1:m2.b2 \n': 'CQ_INCLUDE_TRYBOTS= m1.b1:m2.b2\n', 'Cq_Include_Trybots= m1.b1:m2.b2\n': 'Cq_Include_Trybots= m1.b1:m2.b2\n' } for k, v in expected_results.iteritems(): self.assertEqual(v, self.gerrit._GetCQTryBotLine(k)) @mock.patch.object(time_util, 'GetUTCNow', return_value=datetime.datetime(2017, 2, 7, 1, 0, 0)) def testGetCQFlagsOrExplanationWithinOneDay(self, _): time = datetime.datetime(2017, 2, 7, 0, 0, 0) self.assertEqual( 'No-Presubmit: true\nNo-Tree-Checks: true\nNo-Try: true\n', self.gerrit._GetCQFlagsOrExplanation(time)) def testSubmitRevert(self): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' response = self.http_client._MakeResponse({'change_id': change_id}) self.http_client._SetSubmitRevertResponse(self.server_hostname, change_id, response) self.assertTrue(self.gerrit.SubmitRevert(change_id)) @mock.patch.object(logging, 'error') def testSubmitRevertSubmitRuleFailed(self, mock_logging): change_id = '123456' url = 'https://%s/a/changes/%s/submit' % (self.server_hostname, change_id) response = 'Change 123456: needs Is-Pure-Revert' self.http_client.SetResponse(url, (409, response)) self.assertFalse(self.gerrit.SubmitRevert(change_id)) # Assertions have never worked properly because we were using mock 1.0.1. # After rolling to mock 2.0.0, which fixes assertions, these assertions now # fail. https://crbug.com/947753. # pylint:disable=unused-argument # mock_logging.assert_has_called_with( # 'Committing revert failed: Change 123456: needs Is-Pure-Revert') def testGetChangeIdFromReviewUrl(self): change_id = 'I40bc1e744806f2c4aadf0ce6609aaa61b4019fa7' url = 'https://server.host.name/q/%s' % change_id self.assertEqual(change_id, self.gerrit.GetChangeIdFromReviewUrl(url)) def testGet(self): path_parts = ['changes', '12345', 'detail'] with mock.patch.object(self.gerrit, '_HandleResponse', return_value='return_value'): self.assertEqual(self.gerrit._Get(path_parts), 'return_value') def testQueryClsNoQueryParames(self): self.assertEquals([], self.gerrit.QueryCls({})) def testQueryCls(self): with mock.patch.object( self.gerrit, '_Get', return_value=[{ 'change_id': 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0', 'status': 'MERGED', 'owner': { 'email': '*****@*****.**' }, 'submitted': '2017-02-27 18:56:54.000000000', '_number': 446905, 'reviewers': { 'REVIEWER': [{ 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }, { 'email': '*****@*****.**' }], 'CC': [] }, 'messages': [{ 'id': 'b7d6785c324297ec4f1e6b2de34cf83f4c58e87c', 'author': { 'email': '*****@*****.**' }, 'date': '2017-02-27 18:47:15.000000000', 'message': 'Patch Set 1: Commit-Queue+2', '_revision_number': 1 }, { 'id': 'b9e04aec4bffed0284c1f53cc5a9c88818807368', 'author': { 'email': '*****@*****.**' }, 'date': '2017-02-27 19:04:51.000000000', 'message': 'Created a revert of this change as ' 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1', '_revision_number': 2 }], 'current_revision': 'edda1046ce724695004242e943f59f5e1b2d00ff', 'revisions': { 'edda1046ce724695004242e943f59f5e1b2d00ff': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', }, 'message': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT= True\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\nCommit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' 'BUGS : 12345, 67890', 'parents': [{ 'commit': ('ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f' ), }] }, } }, 'subject': 'subject' }, { 'change_id': 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1', 'status': 'MERGED', 'submitted': '2017-02-27 19:05:03.000000000', 'owner': { 'email': '*****@*****.**' }, '_number': 446788, 'reviewers': { 'REVIEWER': [{ 'email': '*****@*****.**', }, { 'email': '*****@*****.**', }, { 'email': '*****@*****.**', }] }, 'messages': [ { 'id': '30496ce351a43c0b74d812e9e40b440f5acff9d5', 'author': { 'email': '*****@*****.**', }, 'date': '2017-02-27 19:04:53.000000000', 'message': 'Patch Set 1: Code-Review+1 Commit-Queue+2', '_revision_number': 1 }, ], 'current_revision': 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824', 'revisions': { 'bd1db4534d7dc3f3f9c693ca0ac3e67caf56789': { '_number': 1, 'commit': { 'committer': { 'email': '*****@*****.**', 'date': '2017-02-27 19:05:03.000000000', 'tz': 0 }, 'message': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT=TRUE\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\nCommit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' 'BUG: 123455', 'parents': [{ 'commit': ('b63383e4e3f2b774ce0e6d3bfafb4adc84e48f2b' ), }] }, }, 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824': { '_number': 2, 'commit': { 'committer': { 'email': '*****@*****.**', 'date': '2017-02-27 19:05:03.000000000', 'tz': 0 }, 'message': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT=TRUE\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\nCommit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' 'BUG: 123455', 'parents': [{ 'commit': ('b63383e4e3f2b774ce0e6d3bfafb4adc84e48f2b' ), }] }, } }, 'subject': 'subject', 'revert_of': 446905 }]): cls_info = self.gerrit.QueryCls({'author': '*****@*****.**'}) expected_cl_info = [{ 'server_hostname': 'server.host.name', 'auto_revert_off': True, 'owner_email': '*****@*****.**', 'reviewers': [ '*****@*****.**', '*****@*****.**', '*****@*****.**' ], 'closed': True, 'commits': [{ 'patchset_id': 2, 'timestamp': '2017-02-27 18:56:54 UTC', 'revision': 'edda1046ce724695004242e943f59f5e1b2d00ff', 'parent_revisions': ['ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f'] }], 'cc': [], 'subject': 'subject', 'description': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT= True\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\nCommit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' 'BUGS : 12345, 67890', 'change_id': 'I4303e1b7166aaab873587a3fda0ec907d3d8ace0', 'reverts': [], 'commit_attempts': [{ 'patchset_id': 1, 'timestamp': '2017-02-27 18:47:15 UTC', 'committing_user_email': '*****@*****.**' }], 'revert_of': None, 'patchsets': { 'edda1046ce724695004242e943f59f5e1b2d00ff': { 'patchset_id': 2, 'revision': 'edda1046ce724695004242e943f59f5e1b2d00ff', 'parent_revisions': ['ef9ced2eee002d8c143d64d1e66e0a8eaeb8078f'] } } }, { 'server_hostname': 'server.host.name', 'auto_revert_off': True, 'owner_email': '*****@*****.**', 'reviewers': [ '*****@*****.**', '*****@*****.**', '*****@*****.**' ], 'closed': True, 'commits': [{ 'patchset_id': 2, 'timestamp': '2017-02-27 19:05:03 UTC', 'revision': 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824', 'parent_revisions': ['b63383e4e3f2b774ce0e6d3bfafb4adc84e48f2b'] }], 'cc': [], 'subject': 'subject', 'description': 'cl title\n\nsome description\n\n' 'NOAUTOREVERT=TRUE\n\nChange-Id: ' 'someid\nReviewed-on: cl_url\n' 'Commit-Queue: ' 'owner\nReviewed-by: reviewers\n\n' 'BUG: 123455', 'change_id': 'If02ca1cd494579d6bb92a157bf1819e3689cd6b1', 'reverts': [], 'commit_attempts': [{ 'patchset_id': 1, 'timestamp': '2017-02-27 19:04:53 UTC', 'committing_user_email': '*****@*****.**' }], 'revert_of': 446905, 'patchsets': { 'bd1db4534d7dc3f3f9c693ca0ac3e67caf56789': { 'patchset_id': 1, 'revision': 'bd1db4534d7dc3f3f9c693ca0ac3e67caf56789', 'parent_revisions': ['b63383e4e3f2b774ce0e6d3bfafb4adc84e48f2b'] }, 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824': { 'patchset_id': 2, 'revision': 'bd1db4534d7dc3f3f9c693ca0ac3e67caf484824', 'parent_revisions': ['b63383e4e3f2b774ce0e6d3bfafb4adc84e48f2b'] } } }] s_results = [cl_info.serialize() for cl_info in cls_info] self.assertEqual(expected_cl_info, s_results)