| # Copyright 2018 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. |
| |
| """Functions for forgiving false Pre-CQ rejections.""" |
| |
| from __future__ import print_function |
| from __future__ import absolute_import |
| from __future__ import division |
| |
| from chromite.lib import cros_logging as logging |
| |
| from exonerator import gerrit_cq |
| from exonerator import constants |
| |
| |
| _TRYBOT_READY_LABEL = 'Trybot-Ready' |
| _EXONERATOR_PRECQ_MESSAGE = """\ |
| CL-Exonerator has triggered a Pre-CQ retry on this CL due to a Pre-CQ sanity |
| failure. |
| """ |
| _REPOSITORY_WHITELIST = frozenset(['infra/dummies/general-sandbox']) |
| |
| |
| def MaybeExonerate(change, build_id): |
| """Mark a CL as Trybot-Ready only if it hasn't already been. |
| |
| Args: |
| change: A GerritPatchTuple. |
| build_id: The CIDB id for the build which the change was kicked out of. |
| """ |
| helper = gerrit_cq.GetHelper(internal=change.internal) |
| details = helper.GetChangeDetail(change.gerrit_number) |
| if not details: |
| logging.warning('Change details was None for %s', str(change)) |
| return False |
| |
| if not _ShouldBeMarkedPreCQReady(details, change.patch_number): |
| logging.info( |
| "Change %s was not ready to exonerate %d", str(change), build_id) |
| return False |
| |
| if not details['project'] in _REPOSITORY_WHITELIST: |
| logging.info( |
| 'Change %s has project %s, which is not whitelisted.', |
| details, details['project']) |
| |
| try: |
| helper.SetReview( |
| change.gerrit_number, |
| msg=_EXONERATOR_PRECQ_MESSAGE, |
| labels={_TRYBOT_READY_LABEL: 1}) |
| return True |
| # Network errors or permission errors won't get retried. That's ok for now. |
| except Exception: |
| logging.exception('Encountered an exception while marking %s with CQ+1', |
| str(change)) |
| return False |
| |
| |
| def CanBeMarkedReady(change): |
| """Whether a change is ready to be exonerated. |
| |
| Args: |
| change: A GerritPatchTuple to test. |
| """ |
| helper = gerrit_cq.GetHelper(internal=change.internal) |
| details = helper.GetChangeDetail(change.gerrit_number) |
| if details: |
| return _ShouldBeMarkedPreCQReady(details, change.patch_number) |
| else: |
| logging.warning('Change details was None for %s', str(change)) |
| return False |
| |
| |
| def _ShouldBeMarkedPreCQReady(change_details, known_patch_number): |
| """Whether a CL should be re-marked as Trybot-Ready. |
| |
| We only want to send CLs back into the pre-cq if they haven't already been |
| marked Trybot-Ready+1, CQ+1, and it's not already on a newer patchset. |
| |
| Args: |
| change_details: The change details response from Gerrit. |
| known_patch_number: The revision number which we want to forgive. |
| """ |
| return ( |
| gerrit_cq.SanityCheckChange(change_details, known_patch_number) |
| # It shouldn't already have Trybot-Ready+1, CQ+1 or CQ-1 |
| and not (gerrit_cq.LabelValues( |
| change_details, gerrit_cq.COMMIT_QUEUE_LABEL) - {0}) |
| and not (gerrit_cq.LabelValues( |
| change_details, _TRYBOT_READY_LABEL) - {0}) |
| # Don't exonerate CLs with Code-Review-1 or Code-Review-2 |
| and all(label >= 0 |
| for label in gerrit_cq.LabelValues( |
| change_details, gerrit_cq.CODE_REVIEW_LABEL)) |
| and _PreCQApprovalWasRemovedByBot(change_details) |
| ) |
| |
| |
| def _PreCQApprovalWasRemovedByBot(change_details): |
| """Whether the latest Pre-CQ Approval was removed by a bot. |
| |
| Either Trybot-Ready+1 or Commit-Queue+1 labels can kick off pre-cq. To |
| check whether the change was kicked out by a bot, check the latest message |
| which removes either Trybot-Ready or CQ+1, and see whether it was made by |
| chromeos-commit-bot@chromium.org. |
| |
| Args: |
| change_details: The response from gerrit with the change's details. |
| """ |
| for message in reversed(change_details['messages']): |
| if (('-' + _TRYBOT_READY_LABEL) in message['message'] |
| or '-Commit-Queue' in message['message']): |
| return (message['real_author']['email'] |
| == constants.CHROMEOS_COMMIT_BOT_EMAIL) |
| return False |