blob: b946804a412352785001029706e11f7dd6114ffe [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2016 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.
"""Module that generates and sends CL validation messages."""
from __future__ import print_function
from chromite.lib import constants
from chromite.lib import cros_logging as logging
from chromite.lib import gob_util
from chromite.lib import patch as cros_patch
def CreateValidationFailureMessage(pre_cq_trybot,
change,
suspects,
messages,
sanity=True,
infra_fail=False,
lab_fail=False,
no_stat=None,
retry=False):
"""Create a message explaining why a validation failure occurred.
Args:
pre_cq_trybot: Whether the builder is a Pre-CQ trybot. (Note: The Pre-CQ
launcher is NOT considered a Pre-CQ trybot.)
change: The change we want to create a message for.
suspects: An instance of triage_lib.SuspectChanges.
messages: A list of build failure messages from supporting builders.
These must be BuildFailureMessage objects or NoneType objects.
sanity: A boolean indicating whether the build was considered sane. If
not sane, none of the changes will have their CommitReady bit modified.
infra_fail: The build failed purely due to infrastructure failures.
lab_fail: The build failed purely due to test lab infrastructure failures.
no_stat: A list of builders which failed prematurely without reporting
status.
retry: Whether we should retry automatically.
Returns:
A string that communicates what happened.
"""
msg = []
if no_stat:
msg.append('The following build(s) did not start or failed prematurely:')
msg.append(', '.join(no_stat))
if messages:
# Build a list of error messages. We don't want to build a ridiculously
# long comment, as Gerrit will reject it. See https://crbug.com/236831
max_error_len = 20000 / max(1, len(messages))
msg.append('The following build(s) failed:')
for message in [str(x) for x in messages]:
if len(message) > max_error_len:
message = message[:max_error_len] + '... (truncated)'
msg.append(message)
# Create a list of changes other than this one that might be guilty.
# Limit the number of suspects to 20 so that the list of suspects isn't
# ridiculously long.
max_suspects = 20
other_suspects = set(suspects.keys()) - set([change])
if len(other_suspects) < max_suspects:
other_suspects_str = cros_patch.GetChangesAsString(other_suspects)
else:
other_suspects_str = ('%d other changes. See the blamelist for more '
'details.' % (len(other_suspects),))
if not sanity:
msg.append('The build was consider not sane because the sanity check '
'builder(s) failed. Your change will not be blamed for the '
'failure.')
assert retry
elif lab_fail:
msg.append('The build encountered Chrome OS Lab infrastructure issues. '
' Your change will not be blamed for the failure.')
assert retry
else:
if infra_fail:
msg.append('The build failure may have been caused by infrastructure '
'issues and/or bad %s changes.' % constants.INFRA_PROJECTS)
if change in suspects.keys():
if other_suspects_str:
msg.append('Your change may have caused this failure. There are '
'also other changes that may be at fault: %s'
% other_suspects_str)
else:
msg.append('This failure was probably caused by your change.')
msg.append('Please check whether the failure is your fault. If your '
'change is not at fault, you may mark it as ready again.')
else:
if len(suspects) == 1:
msg.append('This failure was probably caused by %s'
% other_suspects_str)
elif len(suspects) > 0:
msg.append('One of the following changes is probably at fault: %s'
% other_suspects_str)
assert retry
if retry:
bot = 'The Pre-Commit Queue' if pre_cq_trybot else 'The Commit Queue'
msg.insert(0, 'NOTE: %s will retry your change automatically.' % bot)
return '\n\n'.join(msg)
class PaladinMessage(object):
"""Object used to send messages to developers about their changes."""
# URL where Paladin documentation is stored.
_PALADIN_DOCUMENTATION_URL = ('https://dev.chromium.org/developers/'
'tree-sheriffs/sheriff-details-chromium-os/'
'commit-queue-overview')
# Gerrit can't handle commands over 32768 bytes. See https://crbug.com/236831
MAX_MESSAGE_LEN = 32000
def __init__(self, message, patch, helper):
if len(message) > self.MAX_MESSAGE_LEN:
message = message[:self.MAX_MESSAGE_LEN] + '... (truncated)'
self.message = message
self.patch = patch
self.helper = helper
def _ConstructPaladinMessage(self):
"""Adds any standard Paladin messaging to an existing message."""
return self.message + ('\n\nCommit queue documentation: %s' %
self._PALADIN_DOCUMENTATION_URL)
def Send(self, dryrun):
"""Posts a comment to a gerrit review."""
body = {
'message': self._ConstructPaladinMessage(),
'notify': 'OWNER',
}
path = 'changes/%s/revisions/%s/review' % (
self.patch.gerrit_number, self.patch.revision)
if dryrun:
logging.info('Would have sent %r to %s', body, path)
return
gob_util.FetchUrl(self.helper.host, path, reqtype='POST', body=body)