Exemplo n.º 1
0
 def ping(self, req):
     log.msg("web ping of builder '%s'" % self.builder_status.getName())
     if not self.getAuthz(req).actionAllowed('pingBuilder', req,
                                             self.builder_status):
         log.msg("..but not authorized")
         return Redirect(path_to_authfail(req))
     c = interfaces.IControl(self.getBuildmaster(req))
     bc = c.getBuilder(self.builder_status.getName())
     bc.ping()
     # send the user back to the builder page
     return Redirect(path_to_builder(req, self.builder_status))
Exemplo n.º 2
0
    def performAction(self, req):
        url = None
        authz = self.getAuthz(req)
        d = authz.actionAllowed(self.action, req, self.builder)
        wfd = defer.waitForDeferred(d)
        yield wfd
        res = wfd.getResult()

        if not res:
            url = path_to_authzfail(req)
        else:
            # get a control object
            c = interfaces.IControl(self.getBuildmaster(req))
            bc = c.getBuilder(self.builder.getName())

            b = self.build_status
            builder_name = self.builder.getName()
            log.msg("web rebuild of build %s:%s" %
                    (builder_name, b.getNumber()))
            name = authz.getUsernameFull(req)
            comments = req.args.get("comments", ["<no reason specified>"])[0]
            reason = ("The web-page 'rebuild' button was pressed by "
                      "'%s': %s\n" % (name, comments))
            msg = ""
            extraProperties = getAndCheckProperties(req)
            if not bc or not b.isFinished() or extraProperties is None:
                msg = "could not rebuild: "
                if b.isFinished():
                    msg += "build still not finished "
                if bc:
                    msg += "could not get builder control"
            else:
                d = bc.rebuildBuild(b, reason, extraProperties)
                wfd = defer.waitForDeferred(d)
                yield wfd
                tup = wfd.getResult()
                # check that (bsid, brids) were properly stored
                if not (isinstance(tup, tuple) and isinstance(tup[0], int)
                        and isinstance(tup[1], dict)):
                    msg = "rebuilding a build failed " + str(tup)
            # we're at
            # http://localhost:8080/builders/NAME/builds/5/rebuild?[args]
            # Where should we send them?
            #
            # Ideally it would be to the per-build page that they just started,
            # but we don't know the build number for it yet (besides, it might
            # have to wait for a current build to finish). The next-most
            # preferred place is somewhere that the user can see tangible
            # evidence of their build starting (or to see the reason that it
            # didn't start). This should be the Builder page.

            url = path_to_builder(req, self.builder), msg
        yield url
Exemplo n.º 3
0
    def force(self, req, auth_ok=False):
        name = req.args.get("username", ["<unknown>"])[0]
        reason = req.args.get("comments", ["<no reason specified>"])[0]
        branch = req.args.get("branch", [""])[0]
        revision = req.args.get("revision", [""])[0]

        r = "The web-page 'force build' button was pressed by '%s': %s\n" \
            % (html.escape(name), html.escape(reason))
        log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'"
                " by user '%s'" %
                (self.builder_status.getName(), branch, revision, name))

        # check if this is allowed
        if not auth_ok:
            if not self.getAuthz(req).actionAllowed('forceBuild', req,
                                                    self.builder_status):
                log.msg("..but not authorized")
                return Redirect(path_to_authfail(req))

        # keep weird stuff out of the branch revision, and property strings.
        # TODO: centralize this somewhere.
        if not re.match(r'^[\w\.\-\/]*$', branch):
            log.msg("bad branch '%s'" % branch)
            return Redirect(path_to_builder(req, self.builder_status))
        if not re.match(r'^[\w\.\-\/]*$', revision):
            log.msg("bad revision '%s'" % revision)
            return Redirect(path_to_builder(req, self.builder_status))
        properties = getAndCheckProperties(req)
        if properties is None:
            return Redirect(path_to_builder(req, self.builder_status))
        if not branch:
            branch = None
        if not revision:
            revision = None

        # TODO: if we can authenticate that a particular User pushed the
        # button, use their name instead of None, so they'll be informed of
        # the results.
        # TODO2: we can authenticate that a particular User pushed the button
        # now, so someone can write this support. but it requires a
        # buildbot.changes.changes.Change instance which is tedious at this
        # stage to compute
        s = SourceStamp(branch=branch, revision=revision)
        try:
            c = interfaces.IControl(self.getBuildmaster(req))
            bc = c.getBuilder(self.builder_status.getName())
            bc.submitBuildRequest(s, r, properties, now=True)
        except interfaces.NoSlaveError:
            # TODO: tell the web user that their request could not be
            # honored
            pass
        # send the user back to the builder page
        return Redirect(path_to_builder(req, self.builder_status))
Exemplo n.º 4
0
 def perspective_requestBuild(self,
                              buildername,
                              reason,
                              branch,
                              revision,
                              properties={}):
     c = interfaces.IControl(self.master)
     bc = c.getBuilder(buildername)
     ss = SourceStamp(branch, revision)
     bpr = Properties()
     bpr.update(properties, "remote requestBuild")
     return bc.submitBuildRequest(ss, reason, bpr)
    def perspective_forcewait(self,
                              builder='build',
                              reason='',
                              branch='',
                              revision='',
                              pdict={}):
        log.msg('forcewait called')

        branch_validate = self.master.config.validation['branch']
        revision_validate = self.master.config.validation['revision']
        pname_validate = self.master.config.validation['property_name']
        pval_validate = self.master.config.validation['property_value']
        if not branch_validate.match(branch):
            log.msg("bad branch '%s'" % branch)
            return
        if not revision_validate.match(revision):
            log.msg("bad revision '%s'" % revision)
            return

        properties = Properties()
        if pdict:
            for prop in pdict:
                pname = prop
                pvalue = pdict[prop]
                if not pname_validate.match(pname) or \
                        not pval_validate.match(pvalue):
                    log.msg("bad property name='%s', value='%s'" %
                            (pname, pvalue))
                    return
                log.msg('set property %s %s' % (pname, pvalue))
                properties.setProperty(pname, pvalue, "Force Build PB")

        c = interfaces.IControl(self.master)
        b = c.getBuilder(builder)

        ss = SourceStamp(branch=branch, revision=revision)

        dr = defer.Deferred()

        def started(s):
            log.msg('force started')
            dr.callback(s.getNumber())

        def requested(breq):
            log.msg('force requested')
            breq.subscribe(started)

        d2 = b.submitBuildRequest(ss, reason, props=properties.asDict())
        d2.addCallback(requested)
        d2.addErrback(log.err, "while forcing a build")

        return dr
Exemplo n.º 6
0
    def performAction(self, req):
        log.msg("web ping of builder '%s'" % self.builder_status.getName())
        res = yield self.getAuthz(req).actionAllowed('pingBuilder', req,
                                                     self.builder_status)
        if not res:
            log.msg("..but not authorized")
            defer.returnValue(path_to_authzfail(req))
            return

        c = interfaces.IControl(self.getBuildmaster(req))
        bc = c.getBuilder(self.builder_status.getName())
        bc.ping()
        # send the user back to the builder page
        defer.returnValue(path_to_builder(req, self.builder_status))
    def _testSlave_1(self, res, t1):
        self.failUnlessEqual(len(t1.events), 2)
        self.failUnlessEqual(t1.events[0],
                             ("builderChangedState", "dummy", "idle"))
        self.failUnlessEqual(t1.events[1],
                             ("builderChangedState", "testdummy", "idle"))
        t1.events = []

        c = interfaces.IControl(self.master)
        req = BuildRequest("forced build for testing", SourceStamp())
        c.getBuilder("dummy").requestBuild(req)
        d = req.waitUntilFinished()
        d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
        dl = defer.DeferredList([d, d2])
        dl.addCallback(self._testSlave_2)
        return dl
Exemplo n.º 8
0
    def stopChangeForBuilder(self, req, builder_status, auth_ok=False):
        try:
            request_change = req.args.get("change", [None])[0]
            request_change = int(request_change)
        except:
            request_change = None

        authz = self.getAuthz(req)
        if request_change:
            c = interfaces.IControl(self.getBuildmaster(req))
            builder_control = c.getBuilder(builder_status.getName())

            wfd = defer.waitForDeferred(
                builder_control.getPendingBuildRequestControls())
            yield wfd
            brcontrols = wfd.getResult()

            build_controls = dict((x.brid, x) for x in brcontrols)

            wfd = defer.waitForDeferred(
                builder_status.getPendingBuildRequestStatuses())
            yield wfd
            build_req_statuses = wfd.getResult()

            for build_req in build_req_statuses:
                wfd = defer.waitForDeferred(build_req.getSourceStamp())
                yield wfd
                ss = wfd.getResult()

                if not ss.changes:
                    continue

                for change in ss.changes:
                    if change.number == request_change:
                        control = build_controls[build_req.brid]
                        log.msg("Cancelling %s" % control)
                        d = authz.actionAllowed('stopChange', req, control)
                        wfd = defer.waitForDeferred(d)
                        yield wfd
                        res = wfd.getResult()
                        if (auth_ok or res):
                            control.cancel()
                        else:
                            yield False
                            return

        yield True
Exemplo n.º 9
0
    def performAction(self, req):
        try:
            request_id = req.args.get("id", [None])[0]
            if request_id == "all":
                cancel_all = True
            else:
                cancel_all = False
                request_id = int(request_id)
        except:
            request_id = None

        authz = self.getAuthz(req)
        if request_id:
            c = interfaces.IControl(self.getBuildmaster(req))
            builder_control = c.getBuilder(self.builder_status.getName())

            brcontrols = yield builder_control.getPendingBuildRequestControls()

            for build_req in brcontrols:
                if cancel_all or (build_req.brid == request_id):
                    log.msg("Cancelling %s" % build_req)
                    res = yield authz.actionAllowed('cancelPendingBuild', req,
                                                    build_req)
                    if res:
                        yield build_req.cancel()
                    else:
                        defer.returnValue(path_to_authzfail(req))
                        return
                    if not cancel_all:
                        break
        args = req.args.copy()

        returnpage = args.get("returnpage", None)

        if returnpage is None:
            defer.returnValue((path_to_builder(req, self.builder_status)))
        elif "builders" in returnpage:
            defer.returnValue(
                (path_to_builders(req, self.builder_status.getProject())))
        elif "buildqueue" in returnpage:
            defer.returnValue(path_to_buildqueue(req))
        elif "builders_json":
            s = self.getStatus(req)
            defer.returnValue(
                (s.getBuildbotURL() +
                 path_to_json_builders(req, self.builder_status.getProject())))
Exemplo n.º 10
0
    def performAction(self, req):
        log.msg("web ping of builder '%s'" % self.builder_status.getName())
        d = self.getAuthz(req).actionAllowed('pingBuilder', req,
                                             self.builder_status)
        wfd = defer.waitForDeferred(d)
        yield wfd
        res = wfd.getResult()
        if not res:
            log.msg("..but not authorized")
            yield path_to_authzfail(req)
            return

        c = interfaces.IControl(self.getBuildmaster(req))
        bc = c.getBuilder(self.builder_status.getName())
        bc.ping()
        # send the user back to the builder page
        yield path_to_builder(req, self.builder_status)
Exemplo n.º 11
0
    def get_candidates(self, builder_names):
        result = []

        master_control = interfaces.IControl(self.master)

        for name in builder_names:
            builder_control = master_control.getBuilder(name)

            pending = yield builder_control.getPendingBuildRequestControls()

            # How can it even return None?
            if pending is None:
                continue

            for buildrequest in pending:
                result.append(
                    (buildrequest, [buildrequest.original_request.source]))

        defer.returnValue(result)
Exemplo n.º 12
0
    def create_master(self, **kwargs):
        assert not self.master, "you called create_master twice"
        # probably because you subclassed RunMixin instead of MasterMixin
        self.slaves = {}
        if self.basedir is None:
            self.basedir = self.mktemp()
        basedir = self.basedir
        os.makedirs(basedir)
        self.master = master.BuildMaster(basedir, **kwargs)
        spec = dbspec.DBSpec.from_url("sqlite:///state.sqlite",
                                      basedir=basedir)

        sm = schema.DBSchemaManager(spec, basedir)
        sm.upgrade(quiet=True)

        self.master.loadDatabase(spec)
        self.master.readConfig = True
        self.master.startService()
        self.status = self.master.getStatus()
        self.control = interfaces.IControl(self.master)
Exemplo n.º 13
0
  def build(self, req):
    [builders, error] = self.getBuilders(req)
    if error is not None:
      return error

    reason = str(req.args.get("comments", [""])[0])
    if len(reason) < 1:
      reason = "<no reason specified>"
    
    [properties, error] = self.getProperties(req)
    if error is not None:
      return error

    log.msg("web force build with properties for builders=[%s]" % (
      " ".join(builders)))

    failed_builders = []
    control = interfaces.IControl(self.getBuildmaster(req))
    source_stamp = SourceStamp(branch=None, revision=None)
    for builder_name in builders:
      bc = control.getBuilder(builder_name)
      if not bc:
        failed_builders.append((builder_name, "Failed to get builder controller"))
        log("could not force build %s - failed to get build control" % (
            builder_name))
        continue
      try:
        bc.submitBuildRequest(source_stamp, reason, properties)
      except interfaces.NoSlaveError:
        failed_builders.append((builder_name, "No slave available"))
    
    if (len(failed_builders) > 0):
      reasons = ["<dt>%s</dt><dd>%s</dd>" % (
                 cgi.escape(x[0]), cgi.escape(x[1])) for x in failed_builders]
      return BuildError("The following builders failed to build: <dl>%s</dl>" % (
                        "\n".join(reasons)), req, False)

    # this isn't an error, but at this point I'm feeling lazy
    # it should realy be a templated html instead
    HTML = "<p>Build request submitted.</p>"
    return BuildError(HTML, req, False)
Exemplo n.º 14
0
    def rebuild(self, req):
        # check auth
        if not self.getAuthz(req).actionAllowed(
                'forceBuild', req, self.build_status.getBuilder()):
            return Redirect(path_to_authfail(req))

        # get a control object
        c = interfaces.IControl(self.getBuildmaster(req))
        bc = c.getBuilder(self.build_status.getBuilder().getName())

        b = self.build_status
        builder_name = b.getBuilder().getName()
        log.msg("web rebuild of build %s:%s" % (builder_name, b.getNumber()))
        name = req.args.get("username", ["<unknown>"])[0]
        comments = req.args.get("comments", ["<no reason specified>"])[0]
        reason = ("The web-page 'rebuild' button was pressed by "
                  "'%s': %s\n" % (name, comments))
        extraProperties = getAndCheckProperties(req)
        if not bc or not b.isFinished() or extraProperties is None:
            log.msg("could not rebuild: bc=%s, isFinished=%s" %
                    (bc, b.isFinished()))
            # TODO: indicate an error
        else:
            d = bc.rebuildBuild(b, reason, extraProperties)
            d.addErrback(log.err, "while rebuilding a build")
        # we're at
        # http://localhost:8080/builders/NAME/builds/5/rebuild?[args]
        # Where should we send them?
        #
        # Ideally it would be to the per-build page that they just started,
        # but we don't know the build number for it yet (besides, it might
        # have to wait for a current build to finish). The next-most
        # preferred place is somewhere that the user can see tangible
        # evidence of their build starting (or to see the reason that it
        # didn't start). This should be the Builder page.

        r = Redirect(path_to_builder(req, self.build_status.getBuilder()))
        d = defer.Deferred()
        reactor.callLater(1, d.callback, r)
        return DeferredResource(d)
Exemplo n.º 15
0
    def get_candidates(self, builder_names):
        result = []

        master_status = self.master.status
        master_control = interfaces.IControl(self.master)

        for name in builder_names:
            builder_status = master_status.getBuilder(name)

            state, current_builds = builder_status.getState()

            if not current_builds:
                continue

            builder_control = master_control.getBuilder(name)

            for build in current_builds:
                source_stamps = yield build.getSourceStamps()
                result.append((builder_control.getBuild(build.getNumber()),
                               source_stamps))

        defer.returnValue(result)
Exemplo n.º 16
0
    def performAction(self, req):
        try:
            request_id = req.args.get("id", [None])[0]
            if request_id == "all":
                cancel_all = True
            else:
                cancel_all = False
                request_id = int(request_id)
        except:
            request_id = None

        authz = self.getAuthz(req)
        if request_id:
            c = interfaces.IControl(self.getBuildmaster(req))
            builder_control = c.getBuilder(self.builder_status.getName())

            wfd = defer.waitForDeferred(
                builder_control.getPendingBuildRequestControls())
            yield wfd
            brcontrols = wfd.getResult()

            for build_req in brcontrols:
                if cancel_all or (build_req.brid == request_id):
                    log.msg("Cancelling %s" % build_req)
                    d = authz.actionAllowed('cancelPendingBuild', req,
                                            build_req)
                    wfd = defer.waitForDeferred(d)
                    yield wfd
                    res = wfd.getResult()
                    if res:
                        build_req.cancel()
                    else:
                        yield path_to_authzfail(req)
                        return
                    if not cancel_all:
                        break

        yield path_to_builder(req, self.builder_status)
Exemplo n.º 17
0
    def performAction(self, req):
        status = self.getStatus(req)
        master = req.site.buildbot_service.master
        c = interfaces.IControl(self.getBuildmaster(req))

        buildrequest = [int(b) for b in req.args.get("cancelselected", []) if b]

        brdicts = yield master.db.buildrequests.getBuildRequestInQueue(brids=buildrequest)

        for brdict in brdicts:
            br = yield BuildRequest.fromBrdict(
                master, brdict)
            b = master.botmaster.builders[brdict['buildername']]
            brc = BuildRequestControl(b, br)
            yield brc.cancel()

        # go back to the buildqueue page
        pending_builds_url = req.args.get("pending_builds_url", None)
        if pending_builds_url:
            defer.returnValue(pending_builds_url[0])
        elif req.args.has_key("ajax"):
            defer.returnValue(path_to_buildqueue_json(req))
        else:
            defer.returnValue(path_to_buildqueue(req))
Exemplo n.º 18
0
 def _testPing_1(self, res):
     d = interfaces.IControl(self.master).getBuilder("dummy").ping(1)
     d.addCallback(self._testPing_2)
     return d
Exemplo n.º 19
0
 def setServiceParent(self, parent):
     base.StatusReceiverMultiService.setServiceParent(self, parent)
     self.f.status = parent.getStatus()
     if self.allowForce:
         self.f.control = interfaces.IControl(parent)
Exemplo n.º 20
0
 def perspective_pingBuilder(self, buildername):
     c = interfaces.IControl(self.master)
     bc = c.getBuilder(buildername)
     bc.ping()
Exemplo n.º 21
0
 def _testRequest_1(self, res):
     c = interfaces.IControl(self.master)
     bss = c.submitBuildSet(["force"], SourceStamp(), "I was bored")  # X
     d = bss.waitUntilFinished()
     return d
Exemplo n.º 22
0
    def performAction(self, req):
        url = None
        authz = self.getAuthz(req)
        res = yield authz.actionAllowed(self.action, req, self.builder)

        if not res:
            url = path_to_authzfail(req)
        else:
            # get a control object
            c = interfaces.IControl(self.getBuildmaster(req))
            bc = c.getBuilder(self.builder.getName())

            b = self.build_status
            builder_name = self.builder.getName()
            log.msg("web rebuild of build %s:%s" %
                    (builder_name, b.getNumber()))
            name = authz.getUsernameFull(req)
            comments = req.args.get("comments", ["<no reason specified>"])[0]
            comments.decode(getRequestCharset(req))

            reason = self.rebuildString % {
                'build_number': b.getNumber(),
                'owner': b.getInterestedUsers(),
                'rebuild_owner': name,
                'rebuild_comments': comments
            }

            useSourcestamp = req.args.get("useSourcestamp", None)
            if useSourcestamp and useSourcestamp == ['updated']:
                absolute = False
            else:
                absolute = True

            msg = ""
            extraProperties = getAndCheckProperties(req)
            if not bc or not b.isFinished() or extraProperties is None:
                msg = "could not rebuild: "
                if b.isFinished():
                    msg += "build still not finished "
                if bc:
                    msg += "could not get builder control"
            else:
                tup = yield bc.rebuildBuild(b,
                                            reason=reason,
                                            extraProperties=extraProperties,
                                            absolute=absolute)
                # rebuildBuild returns None on error (?!)
                if not tup:
                    msg = "rebuilding a build failed " + str(tup)
            # we're at
            # http://localhost:8080/builders/NAME/builds/5/rebuild?[args]
            # Where should we send them?
            #
            # Ideally it would be to the per-build page that they just started,
            # but we don't know the build number for it yet (besides, it might
            # have to wait for a current build to finish). The next-most
            # preferred place is somewhere that the user can see tangible
            # evidence of their build starting (or to see the reason that it
            # didn't start). This should be the Builder page.

            url = path_to_builder(req, self.builder), msg
        defer.returnValue(url)
Exemplo n.º 23
0
 def setServiceParent(self, parent):
     self.f.status = parent
     if self.allowForce:
         self.f.control = interfaces.IControl(self.master)
     return base.StatusReceiverMultiService.setServiceParent(self, parent)
Exemplo n.º 24
0
 def perspective_requestBuild(self, buildername, reason, branch, revision):
     c = interfaces.IControl(self.master)
     bc = c.getBuilder(buildername)
     ss = SourceStamp(branch, revision)
     br = BuildRequest(reason, ss, buildername)
     bc.requestBuild(br)
Exemplo n.º 25
0
    def _testSlave_2(self, res):
        # t1 subscribes to builds, but not anything lower-level
        ev = self.t1.events
        self.failUnlessEqual(len(ev), 4)
        self.failUnlessEqual(ev[0][0:3],
                             ("builderChangedState", "dummy", "building"))
        self.failUnlessEqual(ev[1][0], "buildStarted")
        self.failUnlessEqual(ev[2][0:2] + ev[2][3:4],
                             ("buildFinished", "dummy", builder.SUCCESS))
        self.failUnlessEqual(ev[3][0:3],
                             ("builderChangedState", "dummy", "idle"))

        self.failUnlessEqual(
            [ev[0] for ev in self.t3.events],
            [
                "builderAdded",
                "builderChangedState",  # offline
                "builderAdded",
                "builderChangedState",  # idle
                "builderChangedState",  # offline
                "builderChangedState",  # idle
                "builderChangedState",  # building
                "buildStarted",
                "stepStarted",
                "stepETAUpdate",
                "stepFinished",
                "stepStarted",
                "stepETAUpdate",
                "logStarted",
                "logFinished",
                "stepFinished",
                "buildFinished",
                "builderChangedState",  # idle
            ])

        b = self.s1.getLastFinishedBuild()
        self.failUnless(b)
        self.failUnlessEqual(b.getBuilder().getName(), "dummy")
        self.failUnlessEqual(b.getNumber(), 0)
        self.failUnlessEqual(b.getSourceStamp().branch, None)
        self.failUnlessEqual(b.getSourceStamp().patch, None)
        self.failUnlessEqual(b.getSourceStamp().revision, None)
        self.failUnlessEqual(b.getReason(), "forced build for testing")
        self.failUnlessEqual(b.getChanges(), ())
        self.failUnlessEqual(b.getResponsibleUsers(), [])
        self.failUnless(b.isFinished())
        self.failUnlessEqual(b.getText(), ['build', 'successful'])
        self.failUnlessEqual(b.getColor(), "green")
        self.failUnlessEqual(b.getResults(), builder.SUCCESS)

        steps = b.getSteps()
        self.failUnlessEqual(len(steps), 2)

        eta = 0
        st1 = steps[0]
        self.failUnlessEqual(st1.getName(), "dummy")
        self.failUnless(st1.isFinished())
        self.failUnlessEqual(st1.getText(), ["delay", "1 secs"])
        start, finish = st1.getTimes()
        self.failUnless(0.5 < (finish - start) < 10)
        self.failUnlessEqual(st1.getExpectations(), [])
        self.failUnlessEqual(st1.getLogs(), [])
        eta += finish - start

        st2 = steps[1]
        self.failUnlessEqual(st2.getName(), "remote dummy")
        self.failUnless(st2.isFinished())
        self.failUnlessEqual(st2.getText(), ["remote", "delay", "2 secs"])
        start, finish = st2.getTimes()
        self.failUnless(1.5 < (finish - start) < 10)
        eta += finish - start
        self.failUnlessEqual(st2.getExpectations(), [('output', 38, None)])
        logs = st2.getLogs()
        self.failUnlessEqual(len(logs), 1)
        self.failUnlessEqual(logs[0].getName(), "stdio")
        self.failUnlessEqual(logs[0].getText(), "data")

        self.eta = eta
        # now we run it a second time, and we should have an ETA

        self.t4 = t4 = STarget(["builder", "build", "eta"])
        self.master.getStatus().subscribe(t4)
        c = interfaces.IControl(self.master)
        req = BuildRequest("forced build for testing", SourceStamp())
        c.getBuilder("dummy").requestBuild(req)
        d = req.waitUntilFinished()
        d2 = self.master.botmaster.waitUntilBuilderIdle("dummy")
        dl = defer.DeferredList([d, d2])
        dl.addCallback(self._testSlave_3)
        return dl