blob: 49445e2c67ef66066883ca68a611714bd7709eb3 [file] [log] [blame]
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A StatusReceiver module to warn committers about new failures introduced.
"""
import re
from twisted.python import log
from master import build_utils
from master import chromium_notifier
from master import failures_history
class FailuresNotifier(chromium_notifier.ChromiumNotifier):
"""A status notifier that only alerts committers on new failures.
See builder.interfaces.IStatusReceiver to have more information about the
parameters type."""
# Ignore failures that happened more than this number of times recently.
_IGNORE_FAILURES_THRESHOLD = 1
def __init__(self, **kwargs):
# Set defaults.
kwargs.setdefault('sheriffs', ['sheriff'])
kwargs.setdefault('sendToInterestedUsers', True)
if not kwargs.get('minimum_delay_between_alert'):
kwargs['minimum_delay_between_alert'] = 0
kwargs.setdefault(
'status_header',
'Failure notification for "%(steps)s" on "%(builder)s".')
chromium_notifier.ChromiumNotifier.__init__(self, **kwargs)
# TODO(timurrrr): Make recent_failures an optional argument.
# We might want to use one history object for a few
# FailuresNotifiers (e.g. "ordinary" bots + Webkit bots on Memory FYI)
self.recent_failures = failures_history.FailuresHistory(
expiration_time=12*3600, size_limit=1000)
def isInterestingStep(self, build_status, step_status, results):
"""Look at most cases that could make us ignore the step results.
"""
# If the base class thinks we're not interesting -> skip it.
if not chromium_notifier.ChromiumNotifier.isInterestingStep(
self, build_status, step_status, results):
return False
# Check if the slave is still alive. We should not close the tree for
# inactive slaves.
slave_name = build_status.getSlavename()
if slave_name in self.master_status.getSlaveNames():
# @type self.master_status: L{buildbot.status.builder.Status}
# @type self.parent: L{buildbot.master.BuildMaster}
# @rtype getSlave(): L{buildbot.status.builder.SlaveStatus}
slave_status = self.master_status.getSlave(slave_name)
if slave_status and not slave_status.isConnected():
log.msg('[failurenotifier] Slave %s was disconnected, '
'not sending a warning' % slave_name)
return False
# If all the failure_ids were observed in older builds then this
# failure is not interesting. Also, store the current failure.
has_failures_to_report = False
for l in step_status.getLogs():
failure = l.getName() # stdio or suppression hash or failed test or ?
# TODO(timurrrr): put the gtest regexp into a common place.
if (not re.match(r'^[\dA-F]{16}$', failure) and
not re.match(r'((\w+/)?\w+\.\w+(/\d+)?)', failure)): # gtest name
if failure != 'stdio':
log.msg('[failurenotifier] Log `%s` is ignored since doesn\'t look '
'like a memory suppression hash or test failure.' % failure)
continue
if '.FLAKY_' in failure or '.FAILS_' in failure:
log.msg('[failurenotifier] Ignoring flaky/fails tests: `%s`' % failure)
continue
if (self.recent_failures.GetCount(failure) <
self._IGNORE_FAILURES_THRESHOLD):
has_failures_to_report = True
log.msg('[failurenotifier] Failure `%s` '
'is interesting' % failure)
else:
log.msg('[failurenotifier] Failure `%s` '
'is not interesting - happened to often recently' % failure)
self.recent_failures.Put(failure)
# If we don't have a version stamp nor a blame list, then this is most
# likely a build started manually, and we don't want to issue a warning.
#
# This code is intentionally put after Put calls so we don't send failure
# notifications with the wrong blamelist once bots cycle for the second time
# after a master restart.
latest_revision = build_utils.getLatestRevision(build_status)
if not latest_revision or not build_status.getResponsibleUsers():
log.msg('[failurenotifier] Slave %s failed, but no version stamp, '
'so skipping.' % slave_name)
return False
if has_failures_to_report:
log.msg('[failurenotifier] Decided to send a warning because of slave %s '
'on revision %s' % (slave_name, str(latest_revision)))
return True
else:
log.msg('[failurenotifier] Slave %s revision %s has no interesting '
'failures' % (slave_name, str(latest_revision)))
return False