Repository: buildbot/buildbot

Issue:
Fix reason for Triggerable scheduler.  Add reason tests



Discussion:

Resolving patch:
diff --git a/master/buildbot/steps/slave.py b/master/buildbot/steps/slave.py
--- a/master/buildbot/steps/slave.py
+++ b/master/buildbot/steps/slave.py
@@ -19,7 +19,6 @@
 from buildbot.process import buildstep
 from buildbot.status.results import FAILURE
 from buildbot.status.results import SUCCESS
-from twisted.internet import defer
 
 
 class SlaveBuildStep(buildstep.BuildStep):
@@ -253,7 +252,8 @@ def addLogForRemoteCommands(self, logname):
         self.rc_log = self.addLog(logname)
         return self.rc_log
 
-    def runRemoteCommand(self, cmd, args, abandonOnFailure=True):
+    def runRemoteCommand(self, cmd, args, abandonOnFailure=True,
+                         evaluateCommand=lambda cmd: cmd.didFail()):
         """generic RemoteCommand boilerplate"""
         cmd = buildstep.RemoteCommand(cmd, args)
         cmd.useLog(self.rc_log, False)
@@ -262,7 +262,8 @@ def runRemoteCommand(self, cmd, args, abandonOnFailure=True):
         def commandComplete(cmd):
             if abandonOnFailure and cmd.didFail():
                 raise buildstep.BuildStepFailed()
-            return cmd.didFail()
+            return evaluateCommand(cmd)
+
         d.addCallback(lambda res: commandComplete(cmd))
         return d
 
@@ -272,16 +273,27 @@ def runRmdir(self, dir, **kwargs):
                                      {'dir': dir, 'logEnviron': self.logEnviron},
                                      **kwargs)
 
-    @defer.inlineCallbacks
     def pathExists(self, path):
         """ test whether path exists"""
-        res = yield self.runRemoteCommand('stat', {'file': path,
-                                                   'logEnviron': self.logEnviron, },
-                                          abandonOnFailure=False)
-        defer.returnValue(not res)
+        def commandComplete(cmd):
+            return not cmd.didFail()
+
+        return self.runRemoteCommand('stat', {'file': path,
+                                              'logEnviron': self.logEnviron, },
+                                     abandonOnFailure=False,
+                                     evaluateCommand=commandComplete)
 
     def runMkdir(self, _dir, **kwargs):
         """ create a directory and its parents"""
         return self.runRemoteCommand('mkdir', {'dir': _dir,
                                                'logEnviron': self.logEnviron, },
                                      **kwargs)
+
+    def runGlob(self, glob):
+        """ find files matching a shell-style pattern"""
+        def commandComplete(cmd):
+            return cmd.updates['files'][-1]
+
+        return self.runRemoteCommand('glob', {'glob': glob,
+                                              'logEnviron': self.logEnviron, },
+                                     evaluateCommand=commandComplete)
diff --git a/slave/buildslave/commands/fs.py b/slave/buildslave/commands/fs.py
--- a/slave/buildslave/commands/fs.py
+++ b/slave/buildslave/commands/fs.py
@@ -14,6 +14,7 @@
 # Copyright Buildbot Team Members
 
 import os
+import glob
 import shutil
 import sys
 
@@ -213,6 +214,26 @@ def start(self):
             self.sendStatus({'rc': e.errno})
 
 
+class GlobPath(base.Command):
+
+    header = "glob"
+
+    # args['path'] is relative to Builder directory, and is required.
+    requiredArgs = ['path']
+
+    def start(self):
+        pathname = os.path.join(self.builder.basedir, self.args['path'])
+
+        try:
+            files = glob.glob(pathname)
+            self.sendStatus({'files': files})
+            self.sendStatus({'rc': 0})
+        except OSError, e:
+            log.msg("GlobPath %s failed" % pathname, e)
+            self.sendStatus({'header': '%s: %s: %s' % (self.header, e.strerror, pathname)})
+            self.sendStatus({'rc': e.errno})
+
+
 class ListDir(base.Command):
 
     header = "listdir"
diff --git a/slave/buildslave/commands/registry.py b/slave/buildslave/commands/registry.py
--- a/slave/buildslave/commands/registry.py
+++ b/slave/buildslave/commands/registry.py
@@ -35,6 +35,7 @@
     "rmdir": "buildslave.commands.fs.RemoveDirectory",
     "cpdir": "buildslave.commands.fs.CopyDirectory",
     "stat": "buildslave.commands.fs.StatFile",
+    "glob": "buildslave.commands.fs.GlobPath",
     "listdir": "buildslave.commands.fs.ListDir",
 }
 


Test patch:
diff --git a/master/buildbot/test/unit/test_steps_slave.py b/master/buildbot/test/unit/test_steps_slave.py
--- a/master/buildbot/test/unit/test_steps_slave.py
+++ b/master/buildbot/test/unit/test_steps_slave.py
@@ -368,6 +368,32 @@ def test_mkdir_fail(self):
                            status_text=["generic"])
         return self.runStep()
 
+    def test_glob(self):
+        @defer.inlineCallbacks
+        def testFunc(x):
+            res = yield x.runGlob("*.pyc")
+            self.assertEqual(res, ["one.pyc", "two.pyc"])
+
+        self.setupStep(CompositeUser(testFunc))
+        self.expectCommands(
+            Expect('glob', {'glob': '*.pyc', 'logEnviron': False})
+            + Expect.update('files', ["one.pyc", "two.pyc"])
+            + 0
+        )
+        self.expectOutcome(result=SUCCESS,
+                           status_text=["generic"])
+        return self.runStep()
+
+    def test_glob_fail(self):
+        self.setupStep(CompositeUser(lambda x: x.runGlob("*.pyc")))
+        self.expectCommands(
+            Expect('glob', {'glob': '*.pyc', 'logEnviron': False})
+            + 1
+        )
+        self.expectOutcome(result=FAILURE,
+                           status_text=["generic"])
+        return self.runStep()
+
     def test_abandonOnFailure(self):
         @defer.inlineCallbacks
         def testFunc(x):
diff --git a/slave/buildslave/test/unit/test_commands_fs.py b/slave/buildslave/test/unit/test_commands_fs.py
--- a/slave/buildslave/test/unit/test_commands_fs.py
+++ b/slave/buildslave/test/unit/test_commands_fs.py
@@ -228,6 +228,59 @@ def check(_):
         return d
 
 
+class TestGlobPath(CommandTestMixin, unittest.TestCase):
+
+    def setUp(self):
+        self.setUpCommand()
+
+    def tearDown(self):
+        self.tearDownCommand()
+
+    def test_non_existant(self):
+        self.make_command(fs.GlobPath, dict(
+            path='no-*-file',
+        ), True)
+        d = self.run_command()
+
+        def check(_):
+            self.assertEqual(self.get_updates()[0]['files'], [])
+            self.assertIn({'rc': 0},
+                          self.get_updates(),
+                          self.builder.show())
+        d.addCallback(check)
+        return d
+
+    def test_directory(self):
+        self.make_command(fs.GlobPath, dict(
+            path='[wxyz]or?d*',
+        ), True)
+        d = self.run_command()
+
+        def check(_):
+            self.assertEqual(self.get_updates()[0]['files'], [os.path.join(self.basedir, 'workdir')])
+            self.assertIn({'rc': 0},
+                          self.get_updates(),
+                          self.builder.show())
+        d.addCallback(check)
+        return d
+
+    def test_file(self):
+        self.make_command(fs.GlobPath, dict(
+            path='t*-file',
+        ), True)
+        open(os.path.join(self.basedir, 'test-file'), "w")
+
+        d = self.run_command()
+
+        def check(_):
+            self.assertEqual(self.get_updates()[0]['files'], [os.path.join(self.basedir, 'test-file')])
+            self.assertIn({'rc': 0},
+                          self.get_updates(),
+                          self.builder.show())
+        d.addCallback(check)
+        return d
+
+
 class TestListDir(CommandTestMixin, unittest.TestCase):
 
     def setUp(self):