Repository: buildbot/buildbot

Issue:
Expose builder.tags to web templates.
This tiny PR exposes the builders' "tags" attribute to the templates, allowing me to customize the templates using that feature.



Discussion:

Resolving patch:
diff --git a/master/buildbot/steps/master.py b/master/buildbot/steps/master.py
--- a/master/buildbot/steps/master.py
+++ b/master/buildbot/steps/master.py
@@ -18,6 +18,7 @@
 from twisted.internet import reactor
 from buildbot.process.buildstep import BuildStep
 from buildbot.process.buildstep import SUCCESS, FAILURE
+from twisted.internet import error
 from twisted.internet.protocol import ProcessProtocol
 
 class MasterShellCommand(BuildStep):
@@ -36,7 +37,7 @@ class MasterShellCommand(BuildStep):
 
     def __init__(self, command,
                  description=None, descriptionDone=None, descriptionSuffix=None,
-                 env=None, path=None, usePTY=0,
+                 env=None, path=None, usePTY=0, interruptSignal="KILL",
                  **kwargs):
         BuildStep.__init__(self, **kwargs)
 
@@ -56,6 +57,7 @@ def __init__(self, command,
         self.env=env
         self.path=path
         self.usePTY=usePTY
+        self.interruptSignal = interruptSignal
 
     class LocalPP(ProcessProtocol):
         def __init__(self, step):
@@ -68,7 +70,10 @@ def errReceived(self, data):
             self.step.stdio_log.addStderr(data)
 
         def processEnded(self, status_object):
-            self.step.stdio_log.addHeader("exit status %d\n" % status_object.value.exitCode)
+            if status_object.value.exitCode is not None:
+                self.step.stdio_log.addHeader("exit status %d\n" % status_object.value.exitCode)
+            if status_object.value.signal is not None:
+                self.step.stdio_log.addHeader("signal %s\n" % status_object.value.signal)
             self.step.processEnded(status_object)
 
     def start(self):
@@ -121,12 +126,16 @@ def subst(match):
         stdio_log.addHeader(" env: %r\n" % (env,))
 
         # TODO add a timeout?
-        reactor.spawnProcess(self.LocalPP(self), argv[0], argv,
+        self.process = reactor.spawnProcess(self.LocalPP(self), argv[0], argv,
                 path=self.path, usePTY=self.usePTY, env=env )
         # (the LocalPP object will call processEnded for us)
 
     def processEnded(self, status_object):
-        if status_object.value.exitCode != 0:
+        if status_object.value.signal is not None:
+            self.descriptionDone = ["killed (%s)" % status_object.value.signal]
+            self.step_status.setText(self.describe(done=True))
+            self.finished(FAILURE)
+        elif status_object.value.exitCode != 0:
             self.descriptionDone = ["failed (%d)" % status_object.value.exitCode]
             self.step_status.setText(self.describe(done=True))
             self.finished(FAILURE)
@@ -140,3 +149,12 @@ def describe(self, done=False):
             desc = desc[:]
             desc.extend(self.descriptionSuffix)
         return desc
+
+    def interrupt(self, reason):
+        try:
+            self.process.signalProcess(self.interruptSignal)
+        except KeyError: # Process not started yet
+            pass
+        except error.ProcessExitedAlready:
+            pass
+        BuildStep.interrupt(self, reason)


Test patch:
diff --git a/master/buildbot/test/unit/test_steps_master.py b/master/buildbot/test/unit/test_steps_master.py
--- a/master/buildbot/test/unit/test_steps_master.py
+++ b/master/buildbot/test/unit/test_steps_master.py
@@ -15,12 +15,11 @@
 
 import os
 import sys
-import mock
-from twisted.python import runtime
-from twisted.internet import reactor
+from twisted.python import failure, runtime
+from twisted.internet import error, reactor
 from twisted.trial import unittest
 from buildbot.test.util import steps
-from buildbot.status.results import SUCCESS, FAILURE
+from buildbot.status.results import SUCCESS, FAILURE, EXCEPTION
 from buildbot.steps import master
 from buildbot.process.properties import WithProperties
 
@@ -51,9 +50,11 @@ def spawnProcess(pp, cmd, argv, path, usePTY, env):
                 elif output[0] == 'err':
                     pp.errReceived(output[1])
                 elif output[0] == 'rc':
-                    so = mock.Mock(name='status_object')
-                    so.value.exitCode = output[1]
-                    pp.processEnded(so)
+                    if output[1] != 0:
+                        so = error.ProcessTerminated(exitCode=output[1])
+                    else:
+                        so = error.ProcessDone(None)
+                    pp.processEnded(failure.Failure(so))
         self.patch(reactor, 'spawnProcess', spawnProcess)
 
     def test_real_cmd(self):
@@ -67,6 +68,16 @@ def test_real_cmd(self):
         self.expectOutcome(result=SUCCESS, status_text=["Ran"])
         return self.runStep()
 
+    def test_real_cmd_interrupted(self):
+        cmd = [ sys.executable, '-c', 'while True: pass' ]
+        self.setupStep(
+                master.MasterShellCommand(command=cmd))
+        self.expectLogfile('stdio', "")
+        self.expectOutcome(result=EXCEPTION, status_text=["killed (9)", "interrupted"])
+        d = self.runStep()
+        self.step.interrupt("KILL")
+        return d
+
     def test_real_cmd_fails(self):
         cmd = [ sys.executable, '-c', 'import sys; sys.exit(1)' ]
         self.setupStep(