Repository: buildbot/buildbot

Issue:
re-merge stable into master



Discussion:

Resolving patch:
diff --git a/master/buildbot/schedulers/forcesched.py b/master/buildbot/schedulers/forcesched.py
--- a/master/buildbot/schedulers/forcesched.py
+++ b/master/buildbot/schedulers/forcesched.py
@@ -542,14 +542,16 @@ def gatherPropertiesAndChanges(self, **kwargs):
         defer.returnValue((real_properties, changeids, sourcestamps))
 
     @defer.inlineCallbacks
-    def force(self, owner, builder_name, **kwargs):
+    def force(self, owner, builderNames=None, **kwargs):
         """
         We check the parameters, and launch the build, if everything is correct
         """
-        if not builder_name in self.builderNames:
-            # in the case of buildAll, this method will be called several times
-            # for all the builders
-            # we just do nothing on a builder that is not in our builderNames
+        if builderNames is None:
+            builderNames = self.builderNames
+        else:
+            builderNames = set(builderNames).intersection(self.builderNames)
+
+        if not builderNames:
             defer.returnValue(None)
             return
 
@@ -576,7 +578,7 @@ def force(self, owner, builder_name, **kwargs):
             reason = r,
             sourcestamps = sourcestamps,
             properties = properties,
-            builderNames = [ builder_name ],
+            builderNames = builderNames,
             )
 
         defer.returnValue(res)
diff --git a/master/buildbot/status/web/builder.py b/master/buildbot/status/web/builder.py
--- a/master/buildbot/status/web/builder.py
+++ b/master/buildbot/status/web/builder.py
@@ -31,7 +31,42 @@
 from buildbot import util
 import collections
 
-class ForceAllBuildsActionResource(ActionResource):
+class ForceAction(ActionResource):
+    @defer.inlineCallbacks
+    def force(self, req, builderNames):
+        master = self.getBuildmaster(req)
+        owner = self.getAuthz(req).getUsernameFull(req)
+        schedulername = req.args.get("forcescheduler", ["<unknown>"])[0]
+        if schedulername == "<unknown>":
+            defer.returnValue((path_to_builder(req, self.builder_status),
+                               "forcescheduler arg not found"))
+            return
+
+		args = {}
+        # decode all of the args
+        encoding = getRequestCharset(req)
+        for name, argl in req.args.iteritems():
+           if name == "checkbox":
+               # damn html's ungeneric checkbox implementation...
+               for cb in argl:
+                   args[cb.decode(encoding)] = True
+           else:
+               args[name] = [ arg.decode(encoding) for arg in argl ]
+
+        for sch in master.allSchedulers():
+            if schedulername == sch.name:
+                try:
+                    yield sch.force(owner, builderNames, **args)
+                    msg = ""
+                except ValidationError, e:
+                    msg = html.escape(e.message.encode('ascii','ignore'))
+                break
+
+        # send the user back to the builder page
+        defer.returnValue(msg)
+
+
+class ForceAllBuildsActionResource(ForceAction):
 
     def __init__(self, status, selectedOrAll):
         self.status = status
@@ -47,19 +82,15 @@ def performAction(self, req):
             defer.returnValue(path_to_authzfail(req))
             return
 
-        builders = None
         if self.selectedOrAll == 'all':
-            builders = self.status.getBuilderNames()
+            builderNames = None
         elif self.selectedOrAll == 'selected':
-            builders = [b for b in req.args.get("selected", []) if b]
+            builderNames = [b for b in req.args.get("selected", []) if b]
+
+        msg = yield self.force(req, builderNames)
 
-        for bname in builders:
-            builder_status = self.status.getBuilder(bname)
-            ar = ForceBuildActionResource(builder_status)
-            d = ar.performAction(req)
-            d.addErrback(log.err, "(ignored) while trying to force build")
         # back to the welcome page
-        defer.returnValue(path_to_root(req))
+        defer.returnValue((path_to_root(req) + "builders", msg))
 
 class StopAllBuildsActionResource(ActionResource):
 
@@ -119,7 +150,7 @@ def performAction(self, req):
         # send the user back to the builder page
         defer.returnValue(path_to_builder(req, self.builder_status))
 
-class ForceBuildActionResource(ActionResource):
+class ForceBuildActionResource(ForceAction):
 
     def __init__(self, builder_status):
         self.builder_status = builder_status
@@ -135,35 +166,9 @@ def performAction(self, req):
             defer.returnValue(path_to_authzfail(req))
             return
 
-        master = self.getBuildmaster(req)
-        owner = self.getAuthz(req).getUsernameFull(req)
-        schedulername = req.args.get("forcescheduler", ["<unknown>"])[0]
-        if schedulername == "<unknown>":
-            defer.returnValue((path_to_builder(req, self.builder_status),
-                               "forcescheduler arg not found"))
-            return
-
-        args = req.args.copy()
+        builderName = self.builder_status.getName()
 
-        # decode all of the args
-        encoding = getRequestCharset(req)
-        for name, argl in args.iteritems():
-            args[name] = [ arg.decode(encoding) for arg in argl ]
-
-        # damn html's ungeneric checkbox implementation...
-        for cb in args.get("checkbox", []):
-            args[cb] = True
-
-        builder_name = self.builder_status.getName()
-
-        for sch in master.allSchedulers():
-            if schedulername == sch.name:
-                try:
-                    yield sch.force(owner, builder_name, **args)
-                    msg = ""
-                except ValidationError, e:
-                    msg = html.escape(e.message.encode('ascii','ignore'))
-                break
+        msg = yield self.force(req, [builderName])
 
         # send the user back to the builder page
         defer.returnValue((path_to_builder(req, self.builder_status), msg))


Test patch:
diff --git a/master/buildbot/test/fake/fakedb.py b/master/buildbot/test/fake/fakedb.py
--- a/master/buildbot/test/fake/fakedb.py
+++ b/master/buildbot/test/fake/fakedb.py
@@ -747,6 +747,9 @@ def assertBuildset(self, bsid, expected_buildset, expected_sourcestamps):
         if 'brids' in expected_buildset:
             buildset['brids'] = self.allBuildRequests(bsid)
 
+        if 'builders' in expected_buildset:
+            buildset['builders'] = self.allBuildRequests(bsid).keys()
+
         for ss in dictOfssDict.itervalues():
             if 'id' in ss:
                 del ss['id']
diff --git a/master/buildbot/test/unit/test_schedulers_forcesched.py b/master/buildbot/test/unit/test_schedulers_forcesched.py
--- a/master/buildbot/test/unit/test_schedulers_forcesched.py
+++ b/master/buildbot/test/unit/test_schedulers_forcesched.py
@@ -112,7 +112,7 @@ def test_compare_codebases(self):
     def test_basicForce(self):
         sched = self.makeScheduler()
         
-        res = yield sched.force('user', 'a', branch='a', reason='because',revision='c',
+        res = yield sched.force('user', builderNames=['a'], branch='a', reason='because',revision='c',
                         repository='d', project='p',
                         property1_name='p1',property1_value='e',
                         property2_name='p2',property2_value='f',
@@ -143,6 +143,61 @@ def test_basicForce(self):
                   project='p', sourcestampsetid=100)
              })
 
+    @defer.inlineCallbacks
+    def test_force_allBuilders(self):
+        sched = self.makeScheduler()
+
+        res = yield sched.force('user', branch='a', reason='because',revision='c',
+                        repository='d', project='p',
+                        )
+        bsid,brids = res
+
+        self.assertEqual(len(brids), 2)
+
+        self.db.buildsets.assertBuildset\
+            (bsid,
+             dict(reason="A build was forced by 'user': because",
+                  brids=brids,
+                  builders = ['a', 'b'],
+                  external_idstring=None,
+                  properties=[ ('owner', ('user', 'Force Build Form')),
+                               ('reason', ('because', 'Force Build Form')),
+                               ('scheduler', ('testsched', 'Scheduler')),
+                               ],
+                  sourcestampsetid=100),
+             {'':
+              dict(branch='a', revision='c', repository='d', codebase='',
+                  project='p', sourcestampsetid=100)
+             })
+
+    @defer.inlineCallbacks
+    def test_force_someBuilders(self):
+        sched = self.makeScheduler(builderNames=['a','b','c'])
+
+        res = yield sched.force('user', builderNames=['a','b'],
+                        branch='a', reason='because',revision='c',
+                        repository='d', project='p',
+                        )
+        bsid,brids = res
+
+        self.assertEqual(len(brids), 2)
+
+        self.db.buildsets.assertBuildset\
+            (bsid,
+             dict(reason="A build was forced by 'user': because",
+                  brids=brids,
+                  builders = ['a', 'b'],
+                  external_idstring=None,
+                  properties=[ ('owner', ('user', 'Force Build Form')),
+                               ('reason', ('because', 'Force Build Form')),
+                               ('scheduler', ('testsched', 'Scheduler')),
+                               ],
+                  sourcestampsetid=100),
+             {'':
+              dict(branch='a', revision='c', repository='d', codebase='',
+                  project='p', sourcestampsetid=100)
+             })
+
     def test_bad_codebases(self):
         # cant specify both codebases and branch/revision/project/repository:
         self.assertRaises(ValidationError, ForceScheduler,
@@ -174,7 +229,7 @@ def test_bad_codebases(self):
     @defer.inlineCallbacks
     def test_good_codebases(self):
         sched = self.makeScheduler(codebases=['foo', CodebaseParameter('bar')])
-        res = yield sched.force('user', 'a', reason='because',
+        res = yield sched.force('user', builderNames=['a'], reason='because',
                         foo_branch='a', foo_revision='c', foo_repository='d', foo_project='p',
                         bar_branch='a2', bar_revision='c2', bar_repository='d2', bar_project='p2',
                         property1_name='p1',property1_value='e',
@@ -233,7 +288,7 @@ def do_ParameterTest(self,
         if not req:
             req = {name:value, 'reason':'because'}
         try:
-            bsid, brids = yield sched.force(owner, 'a', **req)
+            bsid, brids = yield sched.force(owner, builderNames=['a'], **req)
         except Exception,e:
             if expectKind is not Exception:
                 # an exception is not expected