Commits

David Schneider committed 7c9ad7d Merge

merge buildbot-update branch, this updates buildbot to 0.8.8

  • Participants
  • Parent commits 4719464, 3781586

Comments (0)

Files changed (20)

 .. -*- mode: rst -*-
 
-Everything has been tested with builbot 0.7.12.  Not sure what happens with
-other versions :-)
+Everything has been tested with builbot 0.8.8.
 
 How to hack the PyPy buildbot
 ==============================
 If you want to run buildbot in production, you need to make sure that the
 function ``pypybuildbot.util.we_are_debugging`` returns ``False`` in your
 environment.  At the moment of writing, debugging is enabled everywhere but on
-wyvern.
+cobra.
 
 You still need to fill ``master/slaveinfo.py`` with the passwords of the
 various slaves you want to use.
 
-Then, to start the buildbot master: ``cd master; make start``
+Then, to start the buildbot master: ``buildbot start <path-to pypy-buildbot/master>``
 
 
 To restart the buildmaster
 
 $ buildbot checkconfig
 
-$ make reconfig
+$ buildbot reconfig
 
 OR
 
-$ make stop
+$ buildbot stop
 
-$ make start
+$ buildbot start
 
 To run a buildslave
 ===================

File bot2/pypybuildbot/arm_master.py

 BUILDJITLINUXARMHF_RASPBIAN = "build-pypy-c-jit-linux-armhf-raspbian"
 BUILDJITLINUXARMHF_RARING = "build-pypy-c-jit-linux-armhf-raring"
 
+builderNames = [
+    APPLVLLINUXARM,
+    APPLVLLINUXARMHF_v7,
+    APPLVLLINUXARMHF_RASPBIAN,
+    JITLINUXARM,
+    JITLINUXARMHF_v7,
+    JITLINUXARMHF_RASPBIAN,
+    JITBACKENDONLYLINUXARMEL,
+    JITBACKENDONLYLINUXARMHF,
+    JITBACKENDONLYLINUXARMHF_v7,
+    BUILDLINUXARM,
+    BUILDJITLINUXARM,
+    BUILDLINUXARMHF_RASPBIAN,
+    BUILDJITLINUXARMHF_RASPBIAN,
+]
 
 schedulers = [
     Nightly("nighly-arm-0-00", [

File bot2/pypybuildbot/builds.py

+from buildbot.steps.source.mercurial import Mercurial
 from buildbot.process import factory
 from buildbot.steps import shell, transfer
 from buildbot.steps.trigger import Trigger
 # while the boards can only run one job at the same time
 ARMBoardLock = locks.SlaveLock('arm_boards', maxCount=1)
 
-
-# XXX monkey patch Trigger class, there are to issues with the list of renderables
-# original: Trigger.renderables = [ 'set_propetries', 'scheduler', 'sourceStamp' ]
-Trigger.renderables = [ 'set_properties', 'schedulerNames', 'sourceStamp' ]
+map_branch_name = lambda x: x if x not in ['', None, 'default'] else 'trunk'
 
 class ShellCmd(shell.ShellCommand):
     # our own version that can distinguish abort cases (rc == -1)
 
     def start(self):
         properties = self.build.getProperties()
-        branch = properties['branch']
-        if branch is None:
-            branch = 'trunk'
+        branch = map_branch_name(properties['branch'])
         #masterdest = properties.render(self.masterdest)
         masterdest = os.path.expanduser(self.masterdest)
         if branch.startswith('/'):
         if not os.path.exists(masterdest):
             os.makedirs(masterdest)
         #
-        assert '%(final_file_name)s' in self.basename
-        symname = self.basename.replace('%(final_file_name)s', 'latest')
+        assert '%(got_revision)s' in self.basename
+        symname = self.basename.replace('%(got_revision)s', 'latest')
         assert '%' not in symname
         self.symlinkname = os.path.join(masterdest, symname)
         #
     def start(self):
 
         properties = self.build.getProperties()
-        branch = properties['branch']
+        branch = map_branch_name(properties['branch'])
         revision = properties['revision']
-
-        if branch is None:
-            branch = 'trunk'
         mastersrc = os.path.expanduser(self.mastersrc)
 
         if branch.startswith('/'):
             branch = branch[1:]
         mastersrc = os.path.join(mastersrc, branch)
-        if revision is not None:
+        if revision:
             basename = WithProperties(self.basename).getRenderingFor(self.build)
             basename = basename.replace(':', '-')
         else:
             builder.summary_by_branch_and_revision = {}
         try:
             rev = properties['got_revision']
-            branch = properties['branch']
-            if branch is None:
-                branch = 'trunk'
+            branch = map_branch_name(properties['branch'])
             if branch.endswith('/'):
                 branch = branch[:-1]
         except KeyError:
 
 # _______________________________________________________________
 
-class UpdateCheckout(ShellCmd):
-    description = 'hg update'
-    command = 'UNKNOWN'
-
-    def __init__(self, workdir=None, haltOnFailure=True, force_branch=None,
-                 **kwargs):
-        ShellCmd.__init__(self, workdir=workdir, haltOnFailure=haltOnFailure,
-                          **kwargs)
-        self.force_branch = force_branch
-        self.addFactoryArguments(force_branch=force_branch)
-
-    def start(self):
-        if self.force_branch is not None:
-            branch = self.force_branch
-            # Note: We could add a warning to the output if we
-            # ignore the branch set by the user.
-        else:
-            properties = self.build.getProperties()
-            branch = properties['branch'] or 'default'
-        command = ["hg", "update", "--clean", "-r", branch]
-        self.setCommand(command)
-        ShellCmd.start(self)
-
-
-class CheckGotRevision(ShellCmd):
-    description = 'got_revision'
-    command = ['hg', 'parents', '--template', 'got_revision:{rev}:{node}']
-
-    def commandComplete(self, cmd):
-        if cmd.rc == 0:
-            got_revision = cmd.logs['stdio'].getText()
-            got_revision = got_revision.split('got_revision:')[-1]
-            # manually get the effect of {node|short} without using a
-            # '|' in the command-line, because it doesn't work on Windows
-            num = got_revision.find(':')
-            if num > 0:
-                got_revision = got_revision[:num + 13]
-            #
-            final_file_name = got_revision.replace(':', '-')
-            # ':' should not be part of filenames --- too many issues
-            self.build.setProperty('got_revision', got_revision,
-                                   'got_revision')
-            self.build.setProperty('final_file_name', final_file_name,
-                                   'got_revision')
-
-
 def update_hg(platform, factory, repourl, workdir, use_branch,
               force_branch=None):
-    if platform == 'win32':
-        command = "if not exist .hg rmdir /q /s ."
-    else:
-        command = "if [ ! -d .hg ]; then rm -fr * .[a-z]*; fi"
-    factory.addStep(ShellCmd(description="rmdir?",
-                             command=command,
-                             workdir=workdir,
-                             haltOnFailure=False))
-    #
-    if platform == "win32":
-        command = "if not exist .hg %s"
-    else:
-        command = "if [ ! -d .hg ]; then %s; fi"
-    command = command % ("hg clone -U " + repourl + " .")
-    factory.addStep(ShellCmd(description="hg clone",
-                             command=command,
-                             workdir=workdir,
-                             timeout=3600,
-                             haltOnFailure=True))
-    #
     factory.addStep(
-        ShellCmd(description="hg purge",
-                 command="hg --config extensions.purge= purge --all",
-                 workdir=workdir,
-                 haltOnFailure=True))
-    #
-    factory.addStep(ShellCmd(description="hg pull",
-                             command="hg pull",
-                             workdir=workdir))
-    #
-    if use_branch or force_branch:
-        factory.addStep(UpdateCheckout(workdir=workdir,
-                                       haltOnFailure=True,
-                                       force_branch=force_branch))
-    else:
-        factory.addStep(ShellCmd(description="hg update",
-                command=WithProperties("hg update --clean %(revision)s"),
-                workdir=workdir))
+            Mercurial(
+                repourl=repourl,
+                mode='incremental',
+                method='fresh',
+                defaultBranch=force_branch,
+                branchType='inrepo',
+                clobberOnBranchChange=False,
+                workdir=workdir,
+                logEnviron=False))
 
 
 def setup_steps(platform, factory, workdir=None,
     update_hg(platform, factory, repourl, workdir, use_branch=True,
               force_branch=force_branch)
     #
-    factory.addStep(CheckGotRevision(workdir=workdir))
+
 
 def build_name(platform, jit=False, flags=[], placeholder=None):
     if placeholder is None:
-        placeholder = '%(final_file_name)s'
+        placeholder = '%(got_revision)s'
     if jit or '-Ojit' in flags:
         kind = 'jit'
     else:
             self.addStep(ShellCmd(
                 description="decompress pypy-c",
                 command=['tar', '--extract', '--file=pypy_build'+ extension, '--strip-components=1', '--directory=.'],
-                workdir='pypy-c'))
+                workdir='pypy-c',
+                haltOnFailure=True,
+                ))
 
         # copy pypy-c to the expected location within the pypy source checkout
         self.addStep(ShellCmd(
             description="move pypy-c",
             command=['cp', '-v', 'pypy-c/bin/pypy', 'build/pypy/goal/pypy-c'],
+            haltOnFailure=True,
             workdir='.'))
         # copy generated and copied header files to build/include
         self.addStep(ShellCmd(
             description="move header files",
             command=['cp', '-vr', 'pypy-c/include', 'build'],
+            haltOnFailure=True,
             workdir='.'))
         # copy ctypes_resource_cache generated during translation
         self.addStep(ShellCmd(
             description="move ctypes resource cache",
             command=['cp', '-rv', 'pypy-c/lib_pypy/ctypes_config_cache', 'build/lib_pypy'],
+            haltOnFailure=True,
             workdir='.'))
 
         add_translated_tests(self, prefix, platform, app_tests, lib_python, pypyjit)
             command=prefix + ["python", "pypy/tool/release/package.py",
                      ".", WithProperties(name), 'pypy',
                      '.'],
+            haltOnFailure=True,
             workdir='build'))
         nightly = '~/nightly/'
         extension = get_extension(platform)
         self.addStep(transfer.FileUpload(slavesrc="benchmarks/result.json",
                                          masterdest=WithProperties(resultfile),
                                          workdir="."))
+
+class PyPyBuildbotTestFactory(factory.BuildFactory):
+    def __init__(self):
+        factory.BuildFactory.__init__(self)
+        # clone
+        self.addStep(
+            Mercurial(
+                repourl='https://bitbucket.org/pypy/buildbot',
+                mode='incremental',
+                method='fresh',
+                defaultBranch='default',
+                branchType='inrepo',
+                clobberOnBranchChange=False,
+                logEnviron=False))
+        # create a virtualenv
+        self.addStep(ShellCmd(
+            description='create virtualenv',
+            haltOnFailure=True,
+            command='virtualenv ../venv'))
+        # install deps
+        self.addStep(ShellCmd(
+            description="install dependencies",
+            haltOnFailure=True,
+            command=('../venv/bin/pip install -r requirements.txt').split()))
+        # run tests
+        self.addStep(PytestCmd(
+            description="pytest buildbot",
+            haltOnFailure=True,
+            command=["../venv/bin/py.test",
+                     "--resultlog=testrun.log",
+                     ],
+            logfiles={'pytestLog': 'testrun.log'}))

File bot2/pypybuildbot/ircbot.py

 the customized IRC messages.
 """
 
-import re
-from buildbot.status.words import Contact, IRC, log
+from buildbot.status.words import IRC, log, IRCContact
 
+# see http://www.mirc.com/colors.html
 USE_COLOR_CODES = True
-GREEN  = '\x033'
-RED    = '\x034'
-AZURE  = '\x0311'
-BLUE   = '\x0312'
-PURPLE = '\x0313'
-GRAY   = '\x0315'
-BOLD   = '\x02'
-def color(code, s):
+BOLD = '\x02'
+COLORS = {
+    'WHITE': '\x030',
+    'BLACK': '\x031',
+    'GREEN': '\x033',
+    'RED': '\x034',
+    'AZURE': '\x0311',
+    'BLUE': '\x0312',
+    'PURPLE': '\x0313',
+    'GRAY': '\x0315',
+}
+
+
+def color(s, code=None, bold=False):
     if USE_COLOR_CODES:
-        return '%s%s\x0F' % (code, s)
+        c = BOLD if bold else ''
+        if code in COLORS:
+            c += COLORS[code]
+        return '%s%s\x0F' % (c, s)
     return s
 
-def extract_username(build):
-    regexp = r"The web-page 'force build' button was pressed by '(.*)': .*"
-    match = re.match(regexp, build.getReason())
-    if match:
-        return match.group(1)
-    return None
+
+def get_build_information(build):
+    owner = build.getProperty("owner")
+    reason = build.getProperty("reason")
+    return ": ".join(k for k in (owner, reason) if k)
 
 
 def get_description_for_build(url, build):
-    url = color(GRAY, url) # in gray
+    url = color(url, 'GRAY')  # in gray
     infos = []
-    username = extract_username(build)
-    if username:
-        infos.append(color(BLUE, username)) # in blue
+    buildinfo = get_build_information(build)
+    if buildinfo:
+        infos.append(color(buildinfo, 'BLUE'))  # in blue
     #
-    branch = build.source.branch
+    branch = build.getProperty('branch')
     if branch:
-        infos.append(color(BOLD, branch)) # in bold
+        infos.append(color(branch, bold=True))  # in bold
     #
     if infos:
         return '%s [%s]' % (url, ', '.join(infos))
     else:
         return url
 
+
 def buildStarted(self, builderName, build):
     builder = build.getBuilder()
-    log.msg('[Contact] Builder %r in category %s started' % (builder, builder.category))
+    log.msg('[Contact] Builder %r in category %s started' %
+                                            (builder, builder.category))
 
     # only notify about builders we are interested in
 
-    if (self.channel.categories != None and
-       builder.category not in self.channel.categories):
+    if (self.bot.categories is not None and
+       builder.category not in self.bot.categories):
         log.msg('Not notifying for a build in the wrong category')
         return
 
         log.msg('Not notifying for a build when started-notification disabled')
         return
 
-    buildurl = self.channel.status.getURLForThing(build)
+    buildurl = self.bot.status.getURLForThing(build)
     descr = get_description_for_build(buildurl, build)
     msg = "Started: %s" % descr
     self.send(msg)
     builder = build.getBuilder()
 
     # only notify about builders we are interested in
-    log.msg('[Contact] builder %r in category %s finished' % (builder, builder.category))
+    log.msg('[Contact] builder %r in category %s finished' %
+                                            (builder, builder.category))
 
-    if (self.channel.categories != None and
-        builder.category not in self.channel.categories):
+    if (self.bot.categories is not None and
+        builder.category not in self.bot.categories):
         return
 
     if not self.notify_for_finished(build):
         return
 
-    buildurl = self.channel.status.getURLForThing(build)
+    buildurl = self.bot.status.getURLForThing(build)
     descr = get_description_for_build(buildurl, build)
-    result = self.results_descriptions.get(build.getResults(), "Finished ??")
-    if result == 'Success':
-        result = color(BOLD+GREEN, result)
-    elif result == 'Exception':
-        result = color(BOLD+PURPLE, result)
-    else:
-        result = color(BOLD+RED, result)
+    result, c = self.results_descriptions.get(build.getResults(),
+                                                ("Finished ??", 'RED'))
+    if c not in COLORS:
+        c = 'RED'
+    result = color(result, c, bold=True)
     msg = "%s: %s" % (result, descr)
     self.send(msg)
 
-Contact.buildStarted = buildStarted
-Contact.buildFinished = buildFinished
+IRCContact.buildStarted = buildStarted
+IRCContact.buildFinished = buildFinished
 
 
 ## def send_message(message, test=False):

File bot2/pypybuildbot/master.py

 
 import os
-import getpass
-from buildbot.scheduler import Nightly, Triggerable
+from buildbot.scheduler import Nightly
+from buildbot.schedulers.forcesched import ForceScheduler
+from buildbot.schedulers.forcesched import ValidationError
 from buildbot.buildslave import BuildSlave
 from buildbot.status.html import WebStatus
-from buildbot.process.builder import Builder
 #from buildbot import manhole
 from pypybuildbot.pypylist import PyPyList, NumpyStatusList
-from pypybuildbot.ircbot import IRC # side effects
+from pypybuildbot.ircbot import IRC  # side effects
 from pypybuildbot.util import we_are_debugging
 
 # Forbid "force build" with empty user name
-from buildbot.status.web.builder import StatusResourceBuilder
-def my_force(self, req, *args, **kwds):
-    name = req.args.get("username", [""])[0]
-    assert name, "Please write your name in the corresponding field."
-    return _previous_force(self, req, *args, **kwds)
-_previous_force = StatusResourceBuilder.force
-if _previous_force.__name__ == 'force':
-    StatusResourceBuilder.force = my_force
-# Done
+class CustomForceScheduler(ForceScheduler):
+    def force(self, owner, builder_name, **kwargs):
+        if not owner:
+            raise ValidationError, "Please write your name in the corresponding field."
+        return ForceScheduler.force(self, owner, builder_name, **kwargs)
+
 
 if we_are_debugging():
     channel = '#buildbot-test'
             JITFREEBSD864,             # on ananke
             JITFREEBSD964,             # on exarkun's freebsd
             JITMACOSX64,               # on xerxes
-            ], branch=None, hour=0, minute=0),
+            ], branch='default', hour=0, minute=0),
 
         Nightly("nightly-2-00", [
             JITBENCH,                  # on tannit32, uses 1 core (in part exclusively)
             JITBENCH64,                # on tannit64, uses 1 core (in part exclusively)
-        ], branch=None, hour=2, minute=0),
+        ], branch='default', hour=2, minute=0),
 
         Nightly("nightly-2-00-py3k", [
             LINUX64,                   # on allegro64, uses all cores
         Nightly("nighly-ppc", [
             JITONLYLINUXPPC64,         # on gcc1
             ], branch='ppc-jit-backend', hour=1, minute=0),
+        CustomForceScheduler('Force Scheduler', 
+            builderNames=[
+                        LINUX32,
+                        LINUX64,
+                        INDIANA32,
+
+                        MACOSX32,
+                        WIN32,
+                        WIN64,
+                        APPLVLLINUX32,
+                        APPLVLLINUX64,
+                        APPLVLWIN32,
+
+                        LIBPYTHON_LINUX32,
+                        LIBPYTHON_LINUX64,
+
+                        JITLINUX32,
+                        JITLINUX64,
+                        JITMACOSX64,
+                        JITWIN32,
+                        JITWIN64,
+                        JITFREEBSD764,
+                        JITFREEBSD864,
+                        JITFREEBSD964,
+                        JITINDIANA32,
+
+                        JITONLYLINUXPPC64,
+                        JITBENCH,
+                        JITBENCH64,
+            ] + ARM.builderNames, properties=[]),
     ] + ARM.schedulers,
 
     'status': [status, ircbot],

File bot2/pypybuildbot/summary.py

 
     def _start_cat_branch(self, cat_branch, fine=False):
         category, branch = cat_branch
-        branch = trunk_name(branch)
+        branch = meta_branch_name(branch)
         category = category_name(category)
 
         self.cur_cat_branch = (category, branch)
         return lambda v: v in membs
 
 def make_subst(v1, v2):
+    if not isinstance(v1, list):
+        v1 = [v1]
     def subst(v):
-        if v == v1:
+        if v in v1:
             return v2
         return v
     return subst
 
-trunk_name = make_subst(None, "<trunk>")
-trunk_value = make_subst("<trunk>", None)
+# Map certain branch names from SourceStamps to a common name shown on the page
+meta_branch_name = make_subst(['default', '', None], '<trunk>')
+# map the meta-branch <trunk> to the actual branch entries from the
+# SourceStamp
+default_value = make_subst('<trunk>', ['default', '', None])
 category_name = make_subst(None, '-')
 nocat_value = make_subst("-", None)
 
 
     def getTitle(self, request):
         status = self.getStatus(request)
-        return "%s: summaries of last %d revisions" % (status.getProjectName(),
-                                                       N)
+        return "%s: summaries of last %d revisions" % (status.getTitle(), N)
 
     @staticmethod
     def _prune_runs(runs, cutnum):
         except KeyError:
             pass
         builder = status.botmaster.builders[builderName]
+        factory = builder.config.factory
         branch = None
-        for _, kw in builder.buildFactory.steps:
+        for step in factory.steps:
+            kw = step.kwargs
             if 'defaultBranch' in kw:
                 if kw.get('explicitBranch'):
                     branch = kw['defaultBranch']
                          only_builder or only_branches)
 
         cat_branches = {}
-
         for builderName in status.getBuilderNames(only_categories):
             if not test_builder(builderName):
                 continue
                 if not test_rev(got_rev):
                     continue
 
+                branch = meta_branch_name(branch)
                 cat_branch = (builderStatus.category, branch)
 
                 runs, no_revision_builds = cat_branches.setdefault(cat_branch,
         only_branches = request.args.get('branch', None)
         only_recentrevs = request.args.get('recentrev', None)
         if only_branches is not None:
-            only_branches = map(trunk_value, only_branches)
+            branches = []
+            for x in map(default_value, only_branches):
+                if isinstance(x, str):
+                    branches.append(x)
+                else:
+                    branches.extend(x)
+            only_branches = branches
         only_builder = request.args.get('builder', None)
         only_builds = None
         if only_builder is not None:
                                          outcome_set_cache.stats()))
 
         if request.args:
-            trunk_vs_any_text = "filter nothing"
-            trunk_vs_any_query = ""
+            default_vs_any_text = "filter nothing"
+            default_vs_any_query = ""
         else:
-            trunk_vs_any_text = "all <trunk>"
-            trunk_vs_any_query = "?branch=<trunk>"
+            default_vs_any_text = "all <trunk>"
+            default_vs_any_query = "?branch=<trunk>"
 
-        trunk_vs_any_anchor = html.a(trunk_vs_any_text,
+        default_vs_any_anchor = html.a(default_vs_any_text,
                                      href="/summary%s" %
-                                     trunk_vs_any_query,
+                                     default_vs_any_query,
                                      class_="failSummary trunkVsAny")
-        trunk_vs_any = html.div(trunk_vs_any_anchor,
+        default_vs_any = html.div(default_vs_any_anchor,
                                 style="position: absolute; right: 5%;")
-        return trunk_vs_any.unicode() + page.render()
+        return default_vs_any.unicode() + page.render()

File bot2/pypybuildbot/test/test_builds.py

 class FakeProperties(object):
 
     def __init__(self):
-        from buildbot.process.properties import PropertyMap
-        self.pmap = PropertyMap(self)
+        pass
     
     def __getitem__(self, item):
         if item == 'branch':
         return FakeDeferred()
 
 def test_Translate():
-    expected = ['translate.py', '--batch', '-O0',
+    expected = ['pypy', '../../rpython/bin/rpython', '--batch', '-O0',
                 'targetpypystandalone', '--no-allworkingmodules']
 
     translateInst = builds.Translate(['-O0'], ['--no-allworkingmodules'])
 
     assert translateInst.command[-len(expected):] == expected
     
-    translateFactory, kw = translateInst.factory
-    rebuiltTranslate = translateFactory(**kw)
+    translateFactory = translateInst._getStepFactory().factory
+    args = translateInst._getStepFactory().args
+    rebuiltTranslate = translateFactory(*args)
                 
     assert rebuiltTranslate.command[-len(expected):] == expected
 
 def test_pypy_upload():
     pth = py.test.ensuretemp('buildbot')
     inst = builds.PyPyUpload(slavesrc='slavesrc', masterdest=str(pth.join('mstr')),
-                             basename='base-%(final_file_name)s', workdir='.',
+                             basename='base-%(got_revision)s', workdir='.',
                              blocksize=100)
-    factory, kw = inst.factory
+    factory = inst._getStepFactory().factory
+    kw = inst._getStepFactory().kwargs
     rebuilt = factory(**kw)
     rebuilt.build = FakeBuild()
     rebuilt.step_status = FakeStepStatus()
     rebuilt.start()
     assert pth.join('mstr').check(dir=True)
     assert rebuilt.masterdest == str(pth.join('mstr', 'trunk',
-                                              'base-123-ea5ca8'))
+                                              'base-123'))
     assert rebuilt.symlinkname == str(pth.join('mstr', 'trunk',
                                                'base-latest'))
 

File bot2/pypybuildbot/test/test_ircbot.py

 from pypybuildbot import ircbot
 
+
 def setup_module(mod):
     ircbot.USE_COLOR_CODES = False
 
+
 def teardown_module(mod):
     ircbot.USE_COLOR_CODES = True
 
+
 class FakeBuild(object):
 
-    def __init__(self, reason=None, source=None):
-        self.reason = reason
-        self.source = source
+    def __init__(self, reason=None, owner=None, branch=None):
+        self.properties = {'owner': owner, 'branch': branch, 'reason': reason}
 
-    def getReason(self):
-        return self.reason
+    def getProperty(self, name):
+        return self.properties.get(name, None)
 
-    def getSourceStamp(self):
-        return self.source
 
-class FakeSource(object):
-
-    def __init__(self, branch):
-        self.branch = branch
-
-def test_extract_username():
-    a = FakeBuild("The web-page 'force build' button was pressed by 'antocuni': foo")
+def test_get_build_information():
+    a = FakeBuild(owner='antocuni',
+            reason="The web-page 'force build' button was pressed")
     b = FakeBuild("The web-page 'force build' button was ...")
-    assert ircbot.extract_username(a) == 'antocuni'
-    assert ircbot.extract_username(b) is None
+    assert ircbot.get_build_information(a) == \
+            "antocuni: The web-page 'force build' button was pressed"
+    assert ircbot.get_build_information(b) == \
+            "The web-page 'force build' button was ..."
 
 
 def test_get_description_for_build():
-    a = FakeBuild('foobar', source=FakeSource(None))
+    a = FakeBuild()
     msg = ircbot.get_description_for_build("http://myurl", a)
     assert msg == "http://myurl"
 
-    a = FakeBuild("The web-page 'force build' button was pressed by 'antocuni': foo",
-                  source=FakeSource(None))
+    a = FakeBuild(owner='antocuni',
+            reason="The web-page 'force build' button was pressed")
     msg = ircbot.get_description_for_build("http://myurl", a)
-    assert msg == "http://myurl [antocuni]"
+    assert msg == "http://myurl [antocuni: " \
+                  + "The web-page 'force build' button was pressed]"
 
-    a = FakeBuild('foobar', source=FakeSource('mybranch'))
+    a = FakeBuild(branch='mybranch')
     msg = ircbot.get_description_for_build("http://myurl", a)
     assert msg == "http://myurl [mybranch]"
 
-    a = FakeBuild("The web-page 'force build' button was pressed by 'antocuni': foo",
-                  source=FakeSource('mybranch'))
+    a = FakeBuild(owner='antocuni', branch='mybranch')
     msg = ircbot.get_description_for_build("http://myurl", a)
     assert msg == "http://myurl [antocuni, mybranch]"

File bot2/pypybuildbot/test/test_pypylist.py

         newdir.setmtime(oldtime + ascii * 10)
     pypylist = PyPyList(tmpdir.strpath)
     listener = pypylist.directoryListing()
-    assert listener.dirs == ['trunk', 'mmmm', 'llll',
+    assert listener.dirs == ['trunk', 'llll',
         'kkkk','jjjj','iiii','hhhh','gggg','ffff','eeee',
         'dddd','cccc','bbbb','aaaa']
 
 def load_BuildmasterConfig():
     import os
-    from pypybuildbot import summary, builds
+    from pypybuildbot import summary, builds, arm_master
     def load(name):
         if name == 'pypybuildbot.summary':
             return summary
         elif name == 'pypybuildbot.builds':
             return builds
+        elif name == 'pypybuildbot.arm_master':
+            return arm_master
         else:
             assert False
 

File bot2/pypybuildbot/test/test_summary.py

 s a/b.py:test_three
 S a/c.py:test_four
 """)
-        
+
         rev_outcome_set.populate(log)
 
         assert rev_outcome_set.skipped == set([("a.b","test_three"),
 x a/c.py:test_nine
 x a/c.py:test_ten
 """)
-        
+
         rev_outcome_set.populate(log)
         sum = rev_outcome_set.get_summary()
         assert sum.p == 1
         rev_outcome_set = summary.RevisionOutcomeSet('0')
         log = StringIO("")
         rev_outcome_set.populate(log)
-        
+
     def test_populate_longrepr(self):
         rev_outcome_set = summary.RevisionOutcomeSet('50000')
         log = StringIO("""F a/b.py:test_one
 s a/b.py:test_three
  some skip
 """)
-        
+
         rev_outcome_set.populate(log)
 
         assert len(rev_outcome_set.skipped) == 1
 F a/b.py:test_two
  \xc3\xa5 bar
 """)
-        
+
         rev_outcome_set.populate(log)
 
         assert len(rev_outcome_set.failed) == 2
 ! <run>
 ! /a/b/c.py:92
 """)
-        
+
         rev_outcome_set.populate(log)
 
         assert rev_outcome_set.failed == set([
         log = StringIO("""x a/b.py
  EXC
 """)
-        
+
         rev_outcome_set.populate(log)
 
         assert rev_outcome_set.numxfailed == 1
-  
-        
+
+
     def test_absent_outcome(self):
         rev_outcome_set = summary.RevisionOutcomeSet('50000')
 
         def load(x, y):
             calls.append(y)
             return y
-        
+
         cache._load_outcome_set = load
 
         res = cache.get('status', 'a')
         cache.get('status', 'b')
         res = cache.get('status', 'c')
         assert res == 'c'
-        
+
         assert calls == ['a', 'b', 'c']
 
         calls = []
         res = cache.get('status', 'd')
         assert res == 'd'
         assert cache.get('status', 'c') == 'c'
-        assert cache.get('status', 'b') == 'b'        
+        assert cache.get('status', 'b') == 'b'
         assert calls == ['d']
 
         res = cache.get('status', 'a')
 s a/b.py:test_three
 x a/b.py:test_four
 """)
-        
+
         rev_outcome_set_foo.populate(log)
 
 
-        key_bar = ('bar', 7)        
+        key_bar = ('bar', 7)
         rev_outcome_set_bar = summary.RevisionOutcomeSet('50000',
                                                          key_bar)
         log = StringIO(""". a/b.py:test_one
 . a/b.py:test_two
 s a/b.py:test_three
 """)
-        
+
         rev_outcome_set_bar.populate(log)
 
         d = {'foo': rev_outcome_set_foo,
         goutcome = summary.GatherOutcomeSet(d)
 
         assert goutcome.revision == '50000'
-        
+
         assert goutcome.failed == set([('foo', 'a.b', 'test_one')])
 
         assert goutcome.skipped == set([('foo', 'a.b', 'test_three'),
         assert res == ' '
 
         res = goutcome_top.get_longrepr(('what', 'foo', 'a.b', 'test_one'))
-        assert res == ''        
+        assert res == ''
 
 def test_colsizes():
     failed = [('a', 'abc', 'd'), ('ab', 'c', 'xy'),
               ('ab', '', 'cd')]
-    
+
     res = summary.colsizes(failed)
-    
+
     assert res == [2,3,2]
 
 def test__prune_runs():
     res = summary.show_elapsed(0.25)
     assert res == "0.25s"
     res = summary.show_elapsed(1.0)
-    assert res == "1.00s"           
+    assert res == "1.00s"
     res = summary.show_elapsed(1.25)
-    assert res == "1.25s"   
+    assert res == "1.25s"
     res = summary.show_elapsed(4.5)
     assert res == "4.50s"
     res = summary.show_elapsed(5.25)
     assert res == "5s"
     res = summary.show_elapsed(5.5)
-    assert res == "6s"            
+    assert res == "6s"
     res = summary.show_elapsed(2*60+30)
     assert res == "2m30"
     res = summary.show_elapsed(4*60+30)
     res = summary.show_elapsed(61*60)
     assert res == "1h1"
     res = summary.show_elapsed(90*60)
-    assert res == "1h30"                
+    assert res == "1h30"
 
-def _BuilderToStatus(status):
 
-    setup = {'name': 'builder', 'builddir': 'BUILDDIR',
-             'slavebuilddir': 'SLAVEBUILDDIR',
-             'factory': process_factory.BuildFactory() }
-    return process_builder.Builder(setup, status)
+class FakeMasterConfig(object):
+    buildbotURL = "http://buildbot/"
+    logCompressionLimit = 0
+    def __init__(self, builders=None):
+        self.builders = builders
+
+
+class FakeBuilderconfig(object):
+    validNames = 'name factory slavenames builddir slavebuilddir category ' \
+                 'nextSlave nextBuild canStartBuild locks env properties ' \
+                 'mergeRequests description'.split()
+
+    def __init__(self, **kwargs):
+        for kw, item in kwargs.iteritems():
+            assert kw in self.validNames
+            setattr(self, kw, item)
 
 
 class FakeMaster(object):
     basedir = None
-    buildbotURL = "http://buildbot/"
 
     def __init__(self, builders):
         self.botmaster = FakeBotMaster(builders)
+        self.config = FakeMasterConfig()
 
     def subscribeToBuildsetCompletions(self, callback):
         pass
     def subscribeToBuildRequests(self, callback):
         pass
 
+
 class FakeBotMaster(object):
 
     def __init__(self, builders):
             self.builderNames.append(name)
             self.builders[name] = _BuilderToStatus(builder)
 
+
 class FakeSite(object):
 
     def __init__(self, status):
         self.buildbot_service = FakeService(status)
 
+
 class FakeService(object):
-    
+
     def __init__(self, status):
         self.status = status
 
     def getStatus(self):
         return self.status
 
+
 class FakeRequest(object):
 
     def __init__(self, builders, args={}):
         self.site = FakeSite(status)
 
 
+def _BuilderToStatus(status):
+    builder = process_builder.Builder(status.name)
+    builder.builder_status = status
+    builder.builder_status.basedir = 'BASEDIR'
+    builder.config = FakeBuilderconfig(factory=process_factory.BuildFactory())
+    return builder
+
+
 def witness_cat_branch(summary):
     ref = [None]
     recentRuns = summary.recentRuns
         ref[0] = cat_branch
         return cat_branch
     summary.recentRuns = witness
-
     return lambda: ref[0]
 
 class FakeLog(object):
         self.step = step
         self.name = name
         self.cont = cont
-        
+
     def getStep(self):
         return self.step
 
     n = getattr(builder, 'nextBuildNumber', 0)
     t = 1000
     for rev, reslog in builds:
-        build = status_builder.BuildStatus(builder, n)
+        build = status_builder.BuildStatus(builder, builder.master, n)
         build.started = time.time()
         build.setProperty('got_revision', str(rev), None)
         step = build.addStepWithName('pytest')
         step.started = t
         step.finished = t + (n+1)*60
         t = step.finished + 30
+        builder.buildCache.cache[build.number] = build
+        builder.buildStarted(build)
         build.buildFinished()
-        builder.touchBuildCache(build)
         n += 1
     builder.nextBuildNumber = n
-        
+
+
+METABRANCH = '<trunk>'
+
 
 class TestSummary(object):
 
     def setup_method(self, meth):
         summary.outcome_set_cache.clear()
+        self.master = FakeMaster([])
 
     def test_sanity(self):
         s = summary.Summary()
         assert cat_branch == {}
 
     def test_one_build_no_rev(self):
-        builder = status_builder.BuilderStatus('builder0')
-        build = status_builder.BuildStatus(builder, 0)
-        build.started = time.time()
+        builder = status_builder.BuilderStatus('builder0', None, self.master, '')
+        build = status_builder.BuildStatus(builder, self.master, 0)
+        build.buildStarted(builder)
         build.buildFinished()
-        builder.touchBuildCache(build)
-        builder.nextBuildNumber = len(builder.buildCache)
+        builder.nextBuildNumber = len(builder.buildCache.cache)
 
         s = summary.Summary()
-        res = witness_cat_branch(s)        
+        res = witness_cat_branch(s)
         req = FakeRequest([builder])
         out = s.body(req)
         cat_branch = res()
 
-        assert cat_branch == {(None, None): ({}, [build])}
+        assert cat_branch == {(None, METABRANCH): ({}, [build])}
 
     def test_one_build_no_logs(self):
-        builder = status_builder.BuilderStatus('builder0')
-        build = status_builder.BuildStatus(builder, 0)
-        build.started = time.time()        
+        builder = status_builder.BuilderStatus('builder0', None, self.master, '')
+        build = status_builder.BuildStatus(builder, self.master, 0)
+        build.started = time.time()
         build.setProperty('got_revision', '50000', None)
         build.buildFinished()
-        builder.touchBuildCache(build)
-        builder.nextBuildNumber = len(builder.buildCache)
+        builder.buildCache.cache[build.number] = build
+        builder.nextBuildNumber = len(builder.buildCache.cache)
 
         s = summary.Summary()
-        res = witness_cat_branch(s)        
+        res = witness_cat_branch(s)
         req = FakeRequest([builder])
         out = s.body(req)
         cat_branch = res()
-        
-        revs = cat_branch[(None, None)][0]
+
+        revs = cat_branch[(None, METABRANCH)][0]
         assert revs.keys() == ['50000']
 
-        assert '&lt;run&gt;' in out
+        assert 'success' in out
 
     def test_one_build_no_logs_failure(self):
-        builder = status_builder.BuilderStatus('builder0')
-        build = status_builder.BuildStatus(builder, 0)
-        build.started = time.time()        
+        builder = status_builder.BuilderStatus('builder0', None, self.master, '')
+        build = status_builder.BuildStatus(builder, self.master, 0)
+        build.started = time.time()
         build.setProperty('got_revision', '50000', None)
         step = build.addStepWithName('step')
         step.setText(['step', 'borken'])
         step.stepFinished(summary.FAILURE)
         step1 = build.addStepWithName('other')
         step1.setText(['other', 'borken'])
-        step1.stepFinished(summary.FAILURE)        
+        step1.stepFinished(summary.FAILURE)
         build.buildFinished()
-        builder.touchBuildCache(build)
-        builder.nextBuildNumber = len(builder.buildCache)
+        builder.buildCache.cache[build.number] = build
+        builder.nextBuildNumber = len(builder.buildCache.cache)
 
         s = summary.Summary()
-        res = witness_cat_branch(s)        
-        req = FakeRequest([builder])
-        out = s.body(req)
-        cat_branch = res()
-        
-        revs = cat_branch[(None, None)][0]
-        assert revs.keys() == ['50000']
-
-        assert 'step borken' in out
-        assert 'other borken' not in out        
-        
-    def test_one_build(self):
-        builder = status_builder.BuilderStatus('builder0')
-        add_builds(builder, [(60000, "F TEST1\n. b")])
-
-        s = summary.Summary()
-        res = witness_cat_branch(s)        
+        res = witness_cat_branch(s)
         req = FakeRequest([builder])
         out = s.body(req)
         cat_branch = res()
 
-        revs = cat_branch[(None, None)][0]
+        revs = cat_branch[(None, METABRANCH)][0]
+        assert revs.keys() == ['50000']
+
+        assert 'step borken' in out
+        assert 'other borken' not in out
+
+    def test_one_build(self):
+        builder = status_builder.BuilderStatus('builder0', None, self.master, '')
+        add_builds(builder, [(60000, "F TEST1\n. b")])
+
+        s = summary.Summary()
+        res = witness_cat_branch(s)
+        req = FakeRequest([builder])
+        out = s.body(req)
+        cat_branch = res()
+
+        revs = cat_branch[(None, METABRANCH)][0]
         assert revs.keys() == ['60000']
         outcome = revs['60000']['builder0']
         assert outcome.revision == '60000'
         assert 'TEST1' in out
 
     def test_two_builds(self):
-        builder = status_builder.BuilderStatus('builder0')
+        builder = status_builder.BuilderStatus('builder0', None, self.master, '')
         add_builds(builder, [('60000', "F TEST1\n. b"),
                              ('60001', ". TEST1\n. b")])
 
         s = summary.Summary()
-        res = witness_cat_branch(s)        
+        res = witness_cat_branch(s)
         req = FakeRequest([builder])
         out = s.body(req)
         cat_branch = res()
 
-        revs = cat_branch[(None, None)][0]
+        revs = cat_branch[(None, METABRANCH)][0]
         assert sorted(revs.keys()) == ['60000', '60001']
         outcome = revs['60000']['builder0']
         assert outcome.revision == '60000'
 
         assert 'TEST1' in out
         assert ':-)' in out
-        assert '\n <a class="failSummary failed" href="javascript:togglestate(1,1)" id="a1c1">-</a> <span class="failSummary success">+</span>  success' in out
-
+        assert re.search(r'\n <a class="failSummary failed" href="javascript:'
+                         r'togglestate\((\d+),(\d+)\)" id="a\1c\2">-</a> '
+                         r'<span class="failSummary success">\+</span>  success', out) is not None
     def test_two_builds_samerev(self):
-        builder = status_builder.BuilderStatus('builder0')
+        builder = status_builder.BuilderStatus('builder0', None, self.master, '')
         add_builds(builder, [('60000', "F TEST1\n. b"),
-                             ('60000', "F TEST1\n. b")])        
+                             ('60000', "F TEST1\n. b")])
 
         s = summary.Summary()
-        res = witness_cat_branch(s)        
+        res = witness_cat_branch(s)
         req = FakeRequest([builder])
         out = s.body(req)
         cat_branch = res()
 
-        revs = cat_branch[(None, None)][0]
+        revs = cat_branch[(None, METABRANCH)][0]
         assert sorted(revs.keys()) == ['60000']
         outcome = revs['60000']['builder0']
         assert outcome.revision == '60000'
         assert 'TEST1' in out
 
     def test_two_builds_recentrev(self):
-        builder = status_builder.BuilderStatus('builder0')
+        builder = status_builder.BuilderStatus('builder0', None, self.master, '')
         add_builds(builder, [('60000', "F TEST1\n. b"),
                              ('60001', "F TEST1\n. b")])
 
         s = summary.Summary()
-        res = witness_cat_branch(s)        
+        res = witness_cat_branch(s)
         req = FakeRequest([builder])
         req.args = {'recentrev': ['60000']}
         out = s.body(req)
         cat_branch = res()
 
-        revs = cat_branch[(None, None)][0]
+        revs = cat_branch[(None, METABRANCH)][0]
         assert sorted(revs.keys()) == ['60000']
         outcome = revs['60000']['builder0']
         assert outcome.revision == '60000'
         assert 'TEST1' in out
 
     def test_many_builds_query_builder(self):
-        builder = status_builder.BuilderStatus('builder0')
+        builder = status_builder.BuilderStatus('builder0', None, self.master, '')
         add_builds(builder, [('60000', "F TEST1\n. b"),
                              ('60000', ". a\n. b"),
-                             ('60001', "F TEST1\n. b")])        
+                             ('60001', "F TEST1\n. b")])
 
         s = summary.Summary()
-        res = witness_cat_branch(s)        
+        res = witness_cat_branch(s)
         req = FakeRequest([builder])
         req.args={'builder': ['builder0']}
         out = s.body(req)
         cat_branch = res()
 
-        runs = cat_branch[(None, None)][0]
+        runs = cat_branch[(None, METABRANCH)][0]
         assert sorted(runs.keys()) == [(0, '60000'), (1, '60000'), (2, '60001')]
         outcome = runs[(0, '60000')]['builder0']
         assert outcome.revision == '60000'
 
 
     def test_many_builds_query_builder_builds(self):
-        builder = status_builder.BuilderStatus('builder0')
+        builder = status_builder.BuilderStatus('builder0', None, self.master, '')
         add_builds(builder, [('60000', "F TEST1\n. b"),
                              ('60000', ". a\n. b"),
-                             ('60001', "F TEST1\n. b")])        
+                             ('60001', "F TEST1\n. b")])
 
         s = summary.Summary()
-        res = witness_cat_branch(s)        
+        res = witness_cat_branch(s)
         req = FakeRequest([builder])
         req.args={'builder': ['builder0'],
                   'builds': ['0','2-2', '7']}
         out = s.body(req)
         cat_branch = res()
 
-        runs = cat_branch[(None, None)][0]
+        runs = cat_branch[(None, METABRANCH)][0]
         assert sorted(runs.keys()) == [(0, '60000'), (2, '60001')]
         outcome = runs[(0, '60000')]['builder0']
         assert outcome.revision == '60000'
         assert 'TEST1' in out
 
     def test_many_pytestLogs(self):
-        builder = status_builder.BuilderStatus('builder1')
-        build = status_builder.BuildStatus(builder, 0)
+        builder = status_builder.BuilderStatus('builder1', '', self.master, '')
+        build = status_builder.BuildStatus(builder, self.master, 0)
         build.started = time.time()
         build.setProperty('got_revision', '70000', None)
         step = build.addStepWithName('pytest')
         step.logs.extend([FakeLog(step, 'pytestLog', "F TEST1")])
         step.setText(["pytest", "failed"])
-        step.stepFinished(summary.FAILURE)        
+        step.stepFinished(summary.FAILURE)
         step2 = build.addStepWithName('pytest2')
         step2.logs.extend([FakeLog(step, 'pytestLog', ". x\nF TEST2")])
         step2.setText(["pytest2", "aborted"])
         step2.stepFinished(summary.EXCEPTION)
         build.buildFinished()
-        builder.touchBuildCache(build)
-        builder.nextBuildNumber = 1
+        builder.buildCache.cache[build.number] = build
+        builder.nextBuildNumber = len(builder.buildCache.cache)
 
         s = summary.Summary()
         req = FakeRequest([builder])
         assert 'pytest2 aborted' in out
 
     def test_subtle_failures(self):
-        builder = status_builder.BuilderStatus('builder1')
-        build = status_builder.BuildStatus(builder, 0)
+        builder = status_builder.BuilderStatus('builder1', '', self.master, '')
+        build = status_builder.BuildStatus(builder, self.master, 0)
         build.started = time.time()
         build.setProperty('got_revision', '70000', None)
-        step = build.addStepWithName('pytest')        
+        step = build.addStepWithName('pytest')
         step.logs.extend([FakeLog(step, 'pytestLog', ". TEST1")])
         step.setText(["pytest", "failed slave lost"])
-        step.stepFinished(summary.FAILURE)        
+        step.stepFinished(summary.FAILURE)
         build.buildFinished()
-        builder.touchBuildCache(build)
-        builder.nextBuildNumber = 1
+        builder.buildCache.cache[build.number] = build
+        builder.nextBuildNumber = len(builder.buildCache.cache)
 
         s = summary.Summary()
         req = FakeRequest([builder])
         out = s.body(req)
 
-        assert 'pytest failed slave lost' in out        
+        assert 'pytest failed slave lost' in out
 
 
     def test_category_branch_sorting_key(self):
         assert res == (2, '', 2, 'release/1')
 
         res = s._cat_branch_key(('', 'what'))
-        assert res == (2, '', 4, 'what')                
+        assert res == (2, '', 4, 'what')
 
     def test_builders_with_categories(self):
-        builder1 = status_builder.BuilderStatus('builder_foo')
-        builder1.category = 'foo'
-        builder2 = status_builder.BuilderStatus('builder_bar')
-        builder2.category = 'bar'
-        builder3 = status_builder.BuilderStatus('builder_')
-        builder3.category = ''
+        builder1 = status_builder.BuilderStatus('builder_foo', 'foo', self.master, '')
+        builder2 = status_builder.BuilderStatus('builder_bar', 'bar', self.master, '')
+        builder3 = status_builder.BuilderStatus('builder_', '', self.master, '')
 
         add_builds(builder1, [('60000', "F TEST1\n")])
         add_builds(builder2, [('60000', "F TEST2\n")])
-        add_builds(builder3, [('60000', "F TEST3\n")])            
+        add_builds(builder3, [('60000', "F TEST3\n")])
 
         s = summary.Summary(['foo', 'bar'])
         req = FakeRequest([builder1, builder2, builder3])
         assert "{bar}" in out
 
     def test_two_builds_different_rev_digits(self):
-        builder = status_builder.BuilderStatus('builder0')
+        builder = status_builder.BuilderStatus('builder0', '', self.master, '')
         add_builds(builder, [(999, "F TEST1\n. b"),
                              (1000, "F TEST1\n. b")])
 
         assert p999builder0-p999 == p1000builder0-p1000+1
 
     def test_build_times_and_filtering(self):
-        builder1 = status_builder.BuilderStatus('builder1')
-        builder2 = status_builder.BuilderStatus('builder2')
- 
+        builder1 = status_builder.BuilderStatus('builder1', '', self.master, '')
+        builder2 = status_builder.BuilderStatus('builder2', '', self.master, '')
+
         add_builds(builder1, [('60000', "F TEST1\n")])
-        add_builds(builder2, [('50000', ". TEST2\n")])        
+        add_builds(builder2, [('50000', ". TEST2\n")])
         add_builds(builder2, [('60000', "F TEST2\n")])
 
         builder1.getBuild(0).started  = 1228258800 # 3 Dec 2008
         builder1.getBuild(0).finished = 1228258800 # 3 Dec 2008
-        builder2.getBuild(1).started  = 1228431600 # 5 Dec 2008        
+        builder2.getBuild(1).started  = 1228431600 # 5 Dec 2008
         builder2.getBuild(1).finished = 1228431600 # 5 Dec 2008
 
         builder2.getBuild(0).started  = 1227913200 # 29 Nov 2008

File bot2/pypybuildbot/util.py

 import socket
 
 def we_are_debugging():
-    return socket.gethostname() not in ("wyvern", "cobra")
+    return socket.gethostname() != 'cobra'
 
 def load(name):
     mod = __import__(name, {}, {}, ['__all__'])

File master/public_html/default.css

 	color: #333;
 }
 
+.auth {
+position:absolute;
+top:5px;
+right:40px;
+}
+
+.alert {
+  color: #c30000;
+  background-color: #f2dcdc;
+  padding: 5px 5px 5px 25px;
+  margin-bottom: 20px;
+  border-top:1px solid #ccc;
+  border-bottom:1px solid #ccc;
+  border-color: #c30000;
+  font-size: 20px;
+}
 a:link,a:visited,a:active {
 	color: #444;
 }
 	font-weight: normal;
 	padding: 8px 8px 8px 8px;
 	color: #333333;
+	background-color: #eee;
+	text-align: left;
+}
+
+td.DevBottom {
 	border-bottom-right-radius: 5px;
 	-webkit-border-bottom-right-radius: 5px;
 	-moz-border-radius-bottomright: 5px;
 	border-bottom-left-radius: 5px;
 	-webkit-border-bottom-left-radius: 5px;
 	-moz-border-radius-bottomleft: 5px;
-	background-color: #eee;
-	text-align: left;
 }
 
 td.Alt {
 }
 
 .legend {
-	border-radius: 5px;
-	-webkit-border-radius: 5px;
-	-moz-border-radius: 5px;
+	border-radius: 5px !important;
+	-webkit-border-radius: 5px !important;
+	-moz-border-radius: 5px !important;
 	width: 100px;
 	max-width: 100px;
 	text-align: center;
 	border-color: #A77272;
 }
 
+.failure-again {
+	color: #000;
+	background-color: #eA9;
+	border-color: #A77272;
+}
+
 .warnings {
 	color: #FFFFFF;
 	background-color: #fa3;
 	border-color: #C5C56D;
 }
 
+.paused {
+    color: #FFFFFF;
+    background-color: #8080FF;
+    border-color: #dddddd;
+}
+
 .offline,td.offline {
     color: #FFFFFF;
     background-color: #777777;
 	display: none;
 }
 
+pre {
+	white-space: pre-wrap;
+}
+
 /* change comments (use regular colors here) */
 pre.comments>a:link,pre.comments>a:visited {
 	color: blue;
 pre.comments>a:active {
 	color: purple;
 }
+
+form.command_forcebuild {
+    border-top: 1px solid black;
+    padding: .5em;
+    margin: .5em;
+}
+
+form.command_forcebuild > .row {
+    border-top: 1px dotted gray;
+    padding: .5em 0;
+}
+
+form.command_forcebuild .force-textarea > .label {
+    display: block;
+}
+
+form.command_forcebuild .force-nested > .label {
+    font-weight: bold;
+    display: list-item;
+}
+
+form.command_forcebuild .force-any .force-text {
+    display: inline;
+}

File master/public_html/favicon.ico

Added
New image

File master/public_html/index.html

 
 <ul>
   <li>the <a href="http://speed.pypy.org/">Performance Plots</a> will give you an overview of performance for recent revisions.</li>
-  <li>the <a href="summary?branch=&lt;trunk&gt;">Summary Display &lt;trunk&gt;</a> will give you a 
-  failure-oriented  summary for recent revisions (&lt;trunk&gt; only).</li>
+  <li>the <a href="summary?branch=default">Summary Display &lt;default&gt;</a> will give you a
+  failure-oriented  summary for recent revisions (&lt;default&gt; only).</li>
 
-  <li>the <a href="summary">Summary Display</a> will give you a 
+  <li>the <a href="summary">Summary Display</a> will give you a
   failure-oriented  summary for recent revisions (all branches).</li>
 
   <li>the <a href="waterfall">Waterfall Display</a> will give you a

File master/public_html/robots.txt

 Disallow: /one_box_per_builder
 Disallow: /xmlrpc
 Disallow: /grid
+Disallow: /tgrid
+Disallow: /json

File master/templates/build.html

 <h1>
 Builder <a href="{{ path_to_builder }}">{{ b.getBuilder().getName() }}</a>
 Build #{{ b.getNumber() }}
-<!-- PyPy specific change: add a "view in summary" linke -->
+</h1>
+<!-- PyPy specific change: add a "view in summary" link -->
 &nbsp;&nbsp;&nbsp;(<a href="{{ path_to_root }}summary?builder={{ b.getBuilder().getName() }}">view in summary</a>)
-</h1>
 
 <div class="column">
 
   {% endif %}
 
   {{ current_step }}
-  
-  {% if authz.advertiseAction('stopBuild') %}
+
+  {% if authz.advertiseAction('stopBuild', request) %}
     <h2>Stop Build</h2>
     {{ forms.stop_build(build_url+"/stop", authz, on_all=False, short=False, label='This Build') }}
   {% endif %}
 {% else %}
   <h2>Results:</h2>
 
-  <p class="{{ result_css }} result">   
+  <p class="{{ result_css }} result">
     {{ b.getText()|join(' ')|capitalize }}
   </p>
-   
+
   {% if b.getTestResults() %}
     <h3><a href="{{ tests_link }}"/></h3>
   {% endif %}
 {% endif %}
 
+<h2>
+{% if sourcestamps|count == 1 %}
+SourceStamp:
+{% else %}
+SourceStamps:
+{% endif %}
+</h2>
 
-<h2>SourceStamp:</h2>
+{% for ss in sourcestamps %}
+<h3>{{ ss.codebase }}</h3>
+    <table class="info" width="100%">
+    {% set ss_class = cycler('alt','') %}
 
-<table class="info" width="100%">
-{% set ss_class = cycler('alt','') %}
+    {% if ss.project %}
+      <tr class="{{ ss_class.next() }}"><td class="left">Project</td><td>{{ ss.project|projectlink }}</td></tr>
+    {% endif %}
 
-{% if ss.project %}
-  <tr class="{{ ss_class.next() }}"><td class="left">Project</td><td>{{ ss.project|projectlink }}</td></tr>
-{% endif %}
+    {% if ss.repository %}
+      <tr class="{{ ss_class.next() }}"><td class="left">Repository</td><td>{{ ss.repository|repolink }}</td></tr>
+    {% endif %}
 
-{% if ss.repository %}
-  <tr class="{{ ss_class.next() }}"><td class="left">Repository</td><td>{{ ss.repository|repolink }}</td></tr>
-{% endif %}
+    {% if ss.branch %}
+      <tr class="{{ ss_class.next() }}"><td class="left">Branch</td><td>{{ ss.branch|e }}</td></tr>
+    {% endif %}
 
-{% if ss.branch %}
-  <tr class="{{ ss_class.next() }}"><td class="left">Branch</td><td>{{ ss.branch|e }}</td></tr>
-{% endif %}
+    {% if ss.revision %}
+      <tr class="{{ ss_class.next() }}"><td class="left">Revision</td><td>{{ ss.revision|revlink(ss.repository) }}</td></tr>
+    {% endif %}
 
-{% if ss.revision %}
-  <tr class="{{ ss_class.next() }}"><td class="left">Revision</td><td>{{ ss.revision|revlink(ss.repository) }}</td></tr>
-{% endif %}
+    {% if got_revisions[ss.codebase] %}
+      <tr class="{{ ss_class.next() }}"><td class="left">Got Revision</td><td>{{ got_revisions[ss.codebase]|revlink(ss.repository) }}</td></tr>
+    {% endif %}
 
-{% if got_revision %}
-  <tr class="{{ ss_class.next() }}"><td class="left">Got Revision</td><td>{{ got_revision|revlink(ss.repository) }}</td></tr>
-{% endif %}
+    {% if ss.patch %}
+      <tr class="{{ ss_class.next() }}"><td class="left">Patch</td><td>YES</td></tr>
+    {% endif %}
 
-{% if ss.patch %}
-  <tr class="{{ ss_class.next() }}"><td class="left">Patch</td><td>YES</td></tr>
-{% endif %}
+    {% if ss.changes %}
+      <tr class="{{ ss_class.next() }}"><td class="left">Changes</td><td><a href="#changes-{{ ss.codebase }}">{{ ss.changes|count }} change{{ 's' if ss.changes|count > 1 else '' }}</a></td></tr>
+    {% endif %}
 
-{% if ss.changes %}
-  <tr class="{{ ss_class.next() }}"><td class="left">Changes</td><td>see below</td></tr>
-{% endif %}
-
-{% if most_recent_rev_build %}
-  <tr class="{{ ss_class.next() }}"><td class="left" colspan="2">Build of most recent revision</td></tr>
-{% endif %}
-
-</table>
+    {% if not ss.branch and not ss.revision and not ss.patch and not ss.changes %}
+      <tr class="{{ ss_class.next() }}"><td class="left" colspan="2">Build of most recent revision</td></tr>
+    {% endif %}
+    </table>
+{% endfor %}
 
 {#
  # TODO: turn this into a table, or some other sort of definition-list
  # that doesn't take up quite so much vertical space
  #}
-   
+
 <h2>BuildSlave:</h2>
-  
-{% if slave_url %}  
+
+{% if slave_url %}
   <a href="{{ slave_url|e }}">{{ b.getSlavename()|e }}</a>
 {% else %}
-  {{ b.getSlavename()|e }} 
+  {{ b.getSlavename()|e }}
 {% endif %}
 
 <h2>Reason:</h2>
 {% for s in steps %}
   <li>
     <div class="{{ s.css_class }} result">
-      <a href="{{ s.link }}">{{ s.name }}</a> 
+      <a href="{{ s.link }}">{{ s.name }}</a>
       {{ s.text }}&nbsp;<span style="float:right">{{ '( ' + s.time_to_run + ' )' if s.time_to_run else '' }}</span>
     </div>
 
       {% else %}
         <li class="{{ item_class.next() }}">- no logs -</li>
       {% endfor %}
-    
+
       {% for u in s.urls %}
         <li class="{{ item_class.next() }}"><a href="{{ u.url }}">{{ u.logname }}</a></li>
       {% endfor %}
-    </ol>  
+    </ol>
   </li>
 {% endfor %}
 </ol>
 <tr><th>Name</th><th>Value</th><th>Source</th></tr>
 
 {% for p in properties %}
+{% if p.source != "Force Build Form" %}
   <tr class="{{ loop.cycle('alt', '') }}">
     <td class="left">{{ p.name|e }}</td>
-  {% if p.short_value %}
-    <td>{{ p.short_value|e }} .. [property value too long]</td>
-  {% else %}
-    <td>{{ p.value|e }}</td>
-  {% endif %}
+    {% if p.short_value %}
+        <td>{{ p.short_value|e }} .. [property value too long]</td>
+    {% else %}
+        {% if p.value is not mapping %}
+            <td>{{ p.value|e }}</td>
+        {% else %}
+            <td>
+                <table class="info" width="100%">
+                    {%- for key, value in p.value.items() recursive %}
+                        <tr><td>{{ key|e }}</td><td>{{ value|e }}</td></tr>
+                    {% endfor %}
+                </table>
+            </td>
+        {% endif %}
+    {% endif %}
     <td>{{ p.source|e }}</td>
   </tr>
+{% endif %}
 {% endfor %}
+</table>
+<h2>Forced Build Properties:</h2>
+<table class="info" width="100%">
+<tr><th>Name</th><th>Label</th><th>Value</th></tr>
 
+{% for p in properties %}
+    {% if p.source == "Force Build Form" %}
+  <tr class="{{ loop.cycle('alt', '') }}">
+    <td class="left">{{ p.name|e }}</td>
+    <td class="left">
+    {% if p.label %}
+    {{ p.label }}
+    {% endif %}
+    </td>
+    {% if p.text %}
+    <td><textarea readonly cols="{{p.cols}}" rows="{{p.rows}}">{{ p.text|e }}</textarea></td>
+    {% else %}
+    <td>{{ p.value|e }}</td>
+    {% endif %}
+  </tr>
+  {% endif %}
+{% endfor %}
 </table>
 
-<h2>Blamelist:</h2>
+<h2>Responsible Users:</h2>
 
 {% if responsible_users %}
   <ol>
   <tr {{ 'class="alt"' if end else '' }}><td class="left">Elapsed</td><td>{{ elapsed }}</td></tr>
 </table>
 
-<!-- PyPy specific change: hide the "resubmit build section"
-  {% if authz.advertiseAction('forceBuild') %}
-    <h3>Resubmit Build:</h3>
-    {{ forms.rebuild_build(build_url+"/rebuild", authz, exactly, ss) }}
-  {% endif %}
--->
+<!-- PyPy specific change: hide the "resubmit build section" --!>
 
 </div>
 
 <br style="clear:both"/>
-  
-{% if ss.changes %}
-<div class="column">
-  <h2>All Changes:</h2>
-  <ol>
-  {% for c in ss.changes %}
-    <li><h3>Change #{{ c.number }}</h3>
-      {{ change(c.asDict()) }}
-    </li>
-  {% else %}
-    <li>no changes</li>
-  {% endfor %}  
-  </ol>
-</div> 
+
+{% if has_changes %}
+    <div class="column">
+      <h2>All Changes:</h2>
+        {% for ss in sourcestamps %}
+            {% if ss.changes %}
+            <h3 id="changes-{{ ss.codebase }}"> {{ ss.codebase }}:</h3>
+            <ol>
+                  {% for c in ss.changes %}
+                    <li><h3>Change #{{ c.number }}</h3>
+                      {{ change(c.asDict()) }}
+                    </li>
+                  {% endfor %}
+            </ol>
+            {% endif %}
+        {% endfor %}
+    </div>
 {% endif %}
 
 {% endblock %}

File master/templates/builder.html

 {% block content %}
 
 <h1>Builder {{ name }}</h1>
-
 <!-- PyPy specific change: add the "view in summary" link -->
 <p>
   (<a href="{{ path_to_root }}waterfall?show={{ name }}">view in waterfall</a>)
   (<a href="{{ path_to_root }}summary?builder={{ name }}">view in summary</a>)
 </p>
 
+{% if description %}
+  <div class="BuilderDescription">{{ description }}</div>
+{% endif %}
+
 <div class="column">
 
 {% if current %}
 
     {{ b.current_step }}
 
-    {% if authz.advertiseAction('stopBuild') %}
+    {% if authz.advertiseAction('stopBuild', request) %}
       {{ forms.stop_build(b.stop_url, authz, on_all=False, short=True, label='Build') }}
-    {% endif %}    
+    {% endif %}
     </li>
   {% endfor %}
   </ul>
 {% else %}
   <h2>No current builds</h2>
-{% endif %}    
- 
+{% endif %}
+
 {% if pending %}
   <h2>Pending Build Requests:</h2>
   <ul>
   {% for b in pending %}
-    <li><small>({{ b.when }}, waiting {{ b.delay }})</small> 
-    
-    {% if authz.advertiseAction('cancelPendingBuild') %}
+    <li><small>({{ b.when }}, waiting {{ b.delay }})</small>
+
+    {% if authz.advertiseAction('cancelPendingBuild', request) %}
       {{ forms.cancel_pending_build(builder_url+"/cancelbuild", authz, short=True, id=b.id) }}
-    {% endif %}    
-    
+    {% endif %}
+
     {% if b.num_changes < 4 %}
         {% for c in b.changes %}{{ c.revision|shortrev(c.repo) }}
-        (<a href="{{ c.url }}">{{ c.who }}</a>){% if not loop.last %},{% endif %}
+        (<a href="{{ c.url }}">{{ c.who|email }}</a>){% if not loop.last %},{% endif %}
         {% endfor %}
     {% else %}
         ({{ b.num_changes }} changes)
-    {% endif %}    
+    {% endif %}
 
+      {% if 'owner' in b.properties %}
+        <b>Forced build</b>
+        by {{b.properties['owner'][0]}}
+        <small>{{b.properties['reason'][0]}}</small>
+      {% endif %}
     </li>
   {% endfor %}
-  </ul>  
-  
-  {% if authz.advertiseAction('cancelPendingBuild') %}
+  </ul>
+
+  {% if authz.advertiseAction('cancelPendingBuild', request) %}
     {{ forms.cancel_pending_build(builder_url+"/cancelbuild", authz, short=False, id='all') }}
-  {% endif %}    
-     
+  {% endif %}
+
 {% else %}
   <h2>No Pending Build Requests</h2>
 {% endif %}
 
 {{ build_table(recent) }}
 
+<a href="?numbuilds={{numbuilds + 5}}">Show more</a>