Imported upstream commit 87fbf3d84711a1d3471ddb36fa16e5eae8bc9464.
☠☠ backed out by ac42a4ad878f ☠ ☠
authorChris AtLee <catlee@mozilla.com>
Wed, 25 Aug 2010 09:34:14 -0400
changeset 83 9cc940a502192e9588c1725f413143c7ade2a8a7
parent 82 298a2f5cfb3e7b1fea4356fa63ade653eabc331a
child 84 645ee7956247fed0cfd8d5494f3d8473c636d694
child 86 ac42a4ad878fd6b6d5e103c1e72190f67c4d2340
push id31
push usercatlee@mozilla.com
push dateThu, 26 Aug 2010 12:50:22 +0000
Imported upstream commit 87fbf3d84711a1d3471ddb36fa16e5eae8bc9464. Add built-in log parsing to LoggingBuildStep Pass BuildStepStatus to log evaluator Add log_eval_func to LoggingBuildStep's parameter list Try log evaluator before guessing about command status RETRY needs to trump all other status' Document worst_status' idea of RETRY better. Remove inaccurate comment about depennding on ordering of status types Add more documentation, rename statut to possible_status
master/buildbot/process/buildstep.py
master/buildbot/status/builder.py
--- a/master/buildbot/process/buildstep.py
+++ b/master/buildbot/process/buildstep.py
@@ -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:
+                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