Import of all commits we need to support status=purple in TinderboxMailNotifier. Includes: 9b5af09f7fa776ef2fad91c2e58dd2e6a4dde4d5, 548d1ace6115c070b4659917536ea7e37e7aa31d, 87fbf3d84711a1d3471ddb36fa16e5eae8bc9464, 5764bd6edf7b639fb91bfe0e5732aefbf0bb6c5e, and df68fd36f4ea9d2e28e409fd1617ddbe537ba67e.
☠☠ backed out by e6338e290f36 ☠ ☠
authorBen Hearsum <bhearsum@mozilla.com>
Wed, 01 Sep 2010 10:22:45 -0400
changeset 89 e200972efcc1add318fc46ede9d6d27b7aa498d4
parent 88 e4a4a686eb12b780cfec25640bbc6ad89b9de3fa
child 90 e6338e290f36d7f382eed4ea56d9c4bf60c42b43
push id35
push userbhearsum@mozilla.com
push dateWed, 01 Sep 2010 14:27:13 +0000
Import of all commits we need to support status=purple in TinderboxMailNotifier. Includes: 9b5af09f7fa776ef2fad91c2e58dd2e6a4dde4d5, 548d1ace6115c070b4659917536ea7e37e7aa31d, 87fbf3d84711a1d3471ddb36fa16e5eae8bc9464, 5764bd6edf7b639fb91bfe0e5732aefbf0bb6c5e, and df68fd36f4ea9d2e28e409fd1617ddbe537ba67e.
master/buildbot/process/buildstep.py
master/buildbot/status/builder.py
master/buildbot/status/tinderbox.py
--- a/master/buildbot/process/buildstep.py
+++ b/master/buildbot/process/buildstep.py
@@ -6,17 +6,17 @@ from twisted.protocols import basic
 from twisted.spread import pb
 from twisted.python import log
 from twisted.python.failure import Failure
 from twisted.web.util import formatFailure
 
 from buildbot import interfaces, locks
 from buildbot.status import progress
 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, \
-     EXCEPTION, RETRY
+     EXCEPTION, RETRY, worst_status
 
 """
 BuildStep and RemoteCommand classes for master-side representation of the
 build process
 """
 
 class RemoteCommand(pb.Referenceable):
     """
@@ -981,27 +981,31 @@ class OutputProgressObserver(LogObserver
 class LoggingBuildStep(BuildStep):
     """This is an abstract base class, suitable for inheritance by all
     BuildSteps that invoke RemoteCommands which emit stdout/stderr messages.
     """
 
     progressMetrics = ('output',)
     logfiles = {}
 
-    parms = BuildStep.parms + ['logfiles', 'lazylogfiles']
+    parms = BuildStep.parms + ['logfiles', 'lazylogfiles', 'log_eval_func']
 
-    def __init__(self, logfiles={}, lazylogfiles=False, *args, **kwargs):
+    def __init__(self, logfiles={}, lazylogfiles=False, log_eval_func=None,
+                 *args, **kwargs):
         BuildStep.__init__(self, *args, **kwargs)
         self.addFactoryArguments(logfiles=logfiles,
-                                 lazylogfiles=lazylogfiles)
+                                 lazylogfiles=lazylogfiles,
+                                 log_eval_func=log_eval_func)
         # merge a class-level 'logfiles' attribute with one passed in as an
         # argument
         self.logfiles = self.logfiles.copy()
         self.logfiles.update(logfiles)
         self.lazylogfiles = lazylogfiles
+        assert not log_eval_func or callable(log_eval_func)
+        self.log_eval_func = log_eval_func
         self.addLogObserver('stdio', OutputProgressObserver("output"))
 
     def addLogFile(self, logname, filename):
         """
         This allows to add logfiles after construction, but before calling
         startCommand().
         """
         self.logfiles[logname] = filename
@@ -1110,19 +1114,20 @@ class LoggingBuildStep(BuildStep):
         """
         pass
 
     def evaluateCommand(self, cmd):
         """Decide whether the command was SUCCESS, WARNINGS, or FAILURE.
         Override this to, say, declare WARNINGS if there is any stderr
         activity, or to say that rc!=0 is not actually an error."""
 
+        if self.log_eval_func:
+            return self.log_eval_func(cmd, self.step_status)
         if cmd.rc != 0:
             return FAILURE
-        # if cmd.log.getStderr(): return WARNINGS
         return SUCCESS
 
     def getText(self, cmd, results):
         if results == SUCCESS:
             return self.describe(True)
         elif results == WARNINGS:
             return self.describe(True) + ["warnings"]
         else:
@@ -1153,13 +1158,36 @@ class LoggingBuildStep(BuildStep):
         return []
 
     def setStatus(self, cmd, results):
         # this is good enough for most steps, but it can be overridden to
         # get more control over the displayed text
         self.step_status.setText(self.getText(cmd, results))
         self.step_status.setText2(self.maybeGetText2(cmd, results))
 
+
+# Parses the logs for a list of regexs. Meant to be invoked like:
+# regexes = ((re.compile(...), FAILURE), (re.compile(...), WARNINGS))
+# self.addStep(ShellCommand,
+#   command=...,
+#   ...,
+#   log_eval_func=lambda c,s: regex_log_evaluator(c, s, regexs)
+# )
+def regex_log_evaluator(cmd, step_status, regexes):
+    worst = SUCCESS
+    for err, possible_status in regexes:
+        # worst_status returns the worse of the two status' passed to it.
+        # we won't be changing "worst" unless possible_status is worse than it,
+        # so we don't even need to check the log if that's the case
+        if worst_status(worst, possible_status) == possible_status:
+            if isinstance(err, (basestring)):
+                err = re.compile(".*%s.*" % err, re.DOTALL)
+            for l in cmd.logs.values():
+                if err.search(l.getText()):
+                    worst = possible_status
+    return worst
+
+
 # (WithProperties used to be available in this module)
 from buildbot.process.properties import WithProperties
 _hush_pyflakes = [WithProperties]
 del _hush_pyflakes
 
--- a/master/buildbot/status/builder.py
+++ b/master/buildbot/status/builder.py
@@ -18,16 +18,23 @@ from bz2 import BZ2File
 from gzip import GzipFile
 
 # sibling imports
 from buildbot import interfaces, util, sourcestamp
 
 SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION, RETRY = range(6)
 Results = ["success", "warnings", "failure", "skipped", "exception", "retry"]
 
+def worst_status(a, b):
+    # SUCCESS > SKIPPED > WARNINGS > FAILURE > EXCEPTION > RETRY
+    # Retry needs to be considered the worst so that conusmers don't have to
+    # worry about other failures undermining the RETRY.
+    for s in (RETRY, EXCEPTION, FAILURE, WARNINGS, SKIPPED, SUCCESS):
+        if s in (a, b):
+            return s
 
 # build processes call the following methods:
 #
 #  setDefaults
 #
 #  currentlyBuilding
 #  currentlyIdle
 #  currentlyInterlocked
--- a/master/buildbot/status/tinderbox.py
+++ b/master/buildbot/status/tinderbox.py
@@ -2,17 +2,17 @@
 from email.Message import Message
 from email.Utils import formatdate
 
 from zope.interface import implements
 from twisted.internet import defer
 
 from buildbot import interfaces
 from buildbot.status import mail
-from buildbot.status.builder import SUCCESS, WARNINGS
+from buildbot.status.builder import SUCCESS, WARNINGS, EXCEPTION, RETRY
 from buildbot.steps.shell import WithProperties
 
 import gzip, bz2, base64, re, cStringIO
 
 # TODO: docs, maybe a test of some sort just to make sure it actually imports
 # and can format email without raising an exception.
 
 class TinderboxMailNotifier(mail.MailNotifier):
@@ -153,16 +153,19 @@ class TinderboxMailNotifier(mail.MailNot
             res = "building"
             text += res
         elif results == SUCCESS:
             res = "success"
             text += res
         elif results == WARNINGS:
             res = "testfailed"
             text += res
+        elif results in (EXCEPTION, RETRY):
+            res = "exception"
+            text += res
         else:
             res += "busted"
             text += res
 
         text += "\n";
 
         if self.columnName is None:
             # use the builder name