| Index: third_party/buildbot_7_12/buildbot/status/web/console.py
|
| diff --git a/third_party/buildbot_7_12/buildbot/status/web/console.py b/third_party/buildbot_7_12/buildbot/status/web/console.py
|
| deleted file mode 100755
|
| index 71d5218711f3b7bc0d8af3d002e684bfeb518330..0000000000000000000000000000000000000000
|
| --- a/third_party/buildbot_7_12/buildbot/status/web/console.py
|
| +++ /dev/null
|
| @@ -1,1072 +0,0 @@
|
| -from __future__ import generators
|
| -
|
| -import time
|
| -import operator
|
| -import re
|
| -import urllib
|
| -
|
| -from buildbot import util
|
| -from buildbot import version
|
| -from buildbot.status import builder
|
| -from buildbot.status.web.base import HtmlResource
|
| -from buildbot.status.web import console_html as res
|
| -from buildbot.status.web import console_js as js
|
| -
|
| -def isBuildGoingToFail(build):
|
| - """Returns True if one of the step in the running build has failed."""
|
| - for step in build.getSteps():
|
| - if step.getResults()[0] == builder.FAILURE:
|
| - return True
|
| - return False
|
| -
|
| -def getInProgressResults(build):
|
| - """Returns build status expectation for an incomplete build."""
|
| - if not build.isFinished() and isBuildGoingToFail(build):
|
| - return builder.FAILURE
|
| -
|
| - return build.getResults()
|
| -
|
| -def getResultsClass(results, prevResults, inProgress, inProgressResults=None):
|
| - """Given the current and past results, returns the class that will be used
|
| - by the css to display the right color for a box."""
|
| -
|
| - if inProgress:
|
| - if inProgressResults == builder.FAILURE:
|
| - return "running_failure"
|
| - return "running"
|
| -
|
| - if results is None:
|
| - return "notstarted"
|
| -
|
| - if results == builder.SUCCESS:
|
| - return "success"
|
| -
|
| - if results == builder.FAILURE:
|
| - if not prevResults:
|
| - # This is the bottom box. We don't know if the previous one failed
|
| - # or not. We assume it did not.
|
| - return "failure"
|
| -
|
| - if prevResults != builder.FAILURE:
|
| - # This is a new failure.
|
| - return "failure"
|
| - else:
|
| - # The previous build also failed.
|
| - return "warnings"
|
| -
|
| - # Any other results? Like EXCEPTION?
|
| - return "exception"
|
| -
|
| -cachedBoxes = dict()
|
| -
|
| -class ANYBRANCH: pass # a flag value, used below
|
| -
|
| -class CachedStatusBox:
|
| - def __init__(self, color, title, details, url, tag):
|
| - self.color = color
|
| - self.title = title
|
| - self.details = details
|
| - self.url = url
|
| - self.tag = tag
|
| -
|
| -
|
| -class CacheStatus:
|
| - def __init__(self):
|
| - self.allBoxes = dict()
|
| - self.lastRevisions = dict()
|
| -
|
| - def display(self):
|
| - data = ""
|
| - for builder in self.allBoxes:
|
| - lastRevision = -1
|
| - try:
|
| - lastRevision = self.lastRevisions[builder]
|
| - except:
|
| - pass
|
| - data += "<br> %s is up to revision %d" % (builder, int(lastRevision))
|
| - for revision in self.allBoxes[builder]:
|
| - data += "<br>%s %s %s" % (builder, revision,
|
| - self.allBoxes[builder][revision].color)
|
| - return data
|
| -
|
| - def insert(self, builderName, revision, color, title, details, url, tag):
|
| - box = CachedStatusBox(color, title, details, url, tag)
|
| - try:
|
| - test = self.allBoxes[builderName]
|
| - except:
|
| - self.allBoxes[builderName] = dict()
|
| -
|
| - self.allBoxes[builderName][revision] = box
|
| -
|
| - def get(self, builderName, revision):
|
| - try:
|
| - return self.allBoxes[builderName][revision]
|
| - except:
|
| - return None
|
| -
|
| - def trim(self):
|
| - for builder in self.allBoxes:
|
| - allRevs = []
|
| - for revision in self.allBoxes[builder]:
|
| - allRevs.append(revision)
|
| -
|
| - if len(allRevs) > 150:
|
| - allRevs.sort()
|
| - deleteCount = len(allRevs) - 150
|
| - for i in range(0, deleteCount):
|
| - del self.allBoxes[builder][allRevs[i]]
|
| -
|
| - def update(self, builderName, lastRevision):
|
| - currentRevision = 0
|
| - try:
|
| - currentRevision = self.lastRevisions[builderName]
|
| - except:
|
| - pass
|
| -
|
| - if currentRevision < lastRevision:
|
| - self.lastRevisions[builderName] = lastRevision
|
| -
|
| - def getRevision(self, builderName):
|
| - try:
|
| - return self.lastRevisions[builderName]
|
| - except:
|
| - return None
|
| -
|
| -
|
| -class TemporaryCache:
|
| - def __init__(self):
|
| - self.lastRevisions = dict()
|
| -
|
| - def display(self):
|
| - data = ""
|
| - for builder in self.lastRevisions:
|
| - data += "<br>%s: %s" % (builder, self.lastRevisions[builder])
|
| -
|
| - return data
|
| -
|
| - def insert(self, builderName, revision):
|
| - currentRevision = 0
|
| - try:
|
| - currentRevision = self.lastRevisions[builderName]
|
| - except:
|
| - pass
|
| -
|
| - if currentRevision < revision:
|
| - self.lastRevisions[builderName] = revision
|
| -
|
| - def updateGlobalCache(self, global_cache):
|
| - for builder in self.lastRevisions:
|
| - global_cache.update(builder, self.lastRevisions[builder])
|
| -
|
| -
|
| -class DevRevision:
|
| - """Helper class that contains all the information we need for a revision."""
|
| -
|
| - def __init__(self, revision, who, comments, date, revlink, when):
|
| - self.revision = revision
|
| - self.comments = comments
|
| - self.who = who
|
| - self.date = date
|
| - self.revlink = revlink
|
| - self.when = when
|
| -
|
| -
|
| -class DevBuild:
|
| - """Helper class that contains all the information we need for a build."""
|
| -
|
| - def __init__(self, revision, results, inProgressResults, number, isFinished,
|
| - text, eta, details, when):
|
| - self.revision = revision
|
| - self.results = results
|
| - self.inProgressResults = inProgressResults
|
| - self.number = number
|
| - self.isFinished = isFinished
|
| - self.text = text
|
| - self.eta = eta
|
| - self.details = details
|
| - self.when = when
|
| -
|
| -
|
| -class ConsoleStatusResource(HtmlResource):
|
| - """Main console class. It displays a user-oriented status page.
|
| - Every change is a line in the page, and it shows the result of the first
|
| - build with this change for each slave."""
|
| -
|
| - def __init__(self, allowForce=True, css=None, orderByTime=False):
|
| - HtmlResource.__init__(self)
|
| -
|
| - self.status = None
|
| - self.control = None
|
| - self.changemaster = None
|
| - self.cache = CacheStatus()
|
| - self.initialRevs = None
|
| -
|
| - self.allowForce = allowForce
|
| - self.css = css
|
| -
|
| - if orderByTime:
|
| - self.comparator = TimeRevisionComparator()
|
| - else:
|
| - self.comparator = IntegerRevisionComparator()
|
| -
|
| - def getTitle(self, request):
|
| - status = self.getStatus(request)
|
| - projectName = status.getProjectName()
|
| - if projectName:
|
| - return "BuildBot: %s" % projectName
|
| - else:
|
| - return "BuildBot"
|
| -
|
| - def getChangemaster(self, request):
|
| - return request.site.buildbot_service.parent.change_svc
|
| -
|
| - def head(self, request):
|
| - jsonFormat = request.args.get("json", [False])[0]
|
| - if jsonFormat:
|
| - return ""
|
| -
|
| - # Start by adding all the javascript functions we have.
|
| - head = "<script type='text/javascript'> %s </script>" % js.JAVASCRIPT
|
| -
|
| - reload_time = None
|
| - # Check if there was an arg. Don't let people reload faster than
|
| - # every 15 seconds. 0 means no reload.
|
| - if "reload" in request.args:
|
| - try:
|
| - reload_time = int(request.args["reload"][0])
|
| - if reload_time != 0:
|
| - reload_time = max(reload_time, 15)
|
| - except ValueError:
|
| - pass
|
| -
|
| - # Append the tag to refresh the page.
|
| - if reload_time is not None and reload_time != 0:
|
| - head += '<meta http-equiv="refresh" content="%d">\n' % reload_time
|
| - return head
|
| -
|
| -
|
| - ##
|
| - ## Data gathering functions
|
| - ##
|
| -
|
| - def getHeadBuild(self, builder):
|
| - """Get the most recent build for the given builder.
|
| - """
|
| - build = builder.getBuild(-1)
|
| -
|
| - # HACK: Work around #601, the head build may be None if it is
|
| - # locked.
|
| - if build is None:
|
| - build = builder.getBuild(-2)
|
| -
|
| - return build
|
| -
|
| - def fetchChangesFromHistory(self, status, max_depth, max_builds, debugInfo):
|
| - """Look at the history of the builders and try to fetch as many changes
|
| - as possible. We need this when the main source does not contain enough
|
| - sourcestamps.
|
| -
|
| - max_depth defines how many builds we will parse for a given builder.
|
| - max_builds defines how many builds total we want to parse. This is to
|
| - limit the amount of time we spend in this function.
|
| -
|
| - This function is sub-optimal, but the information returned by this
|
| - function is cached, so this function won't be called more than once.
|
| - """
|
| -
|
| - allChanges = list()
|
| - build_count = 0
|
| - for builderName in status.getBuilderNames()[:]:
|
| - if build_count > max_builds:
|
| - break
|
| -
|
| - builder = status.getBuilder(builderName)
|
| - build = self.getHeadBuild(builder)
|
| - depth = 0
|
| - while build and depth < max_depth and build_count < max_builds:
|
| - depth += 1
|
| - build_count += 1
|
| - sourcestamp = build.getSourceStamp()
|
| - allChanges.extend(sourcestamp.changes[:])
|
| - build = build.getPreviousBuild()
|
| -
|
| - debugInfo["source_fetch_len"] = len(allChanges)
|
| - return allChanges
|
| -
|
| - def getAllChanges(self, source, status, debugInfo):
|
| - """Return all the changes we can find at this time. If |source| does not
|
| - not have enough (less than 25), we try to fetch more from the builders
|
| - history."""
|
| -
|
| - allChanges = list()
|
| - allChanges.extend(source.changes[:])
|
| -
|
| - debugInfo["source_len"] = len(source.changes)
|
| -
|
| - if len(allChanges) < 25:
|
| - # There is not enough revisions in the source.changes. It happens
|
| - # quite a lot because buildbot mysteriously forget about changes
|
| - # once in a while during restart.
|
| - # Let's try to get more changes from the builders.
|
| - # We check the last 10 builds of all builders, and stop when we
|
| - # are done, or have looked at 100 builds.
|
| - # We do this only once!
|
| - if not self.initialRevs:
|
| - self.initialRevs = self.fetchChangesFromHistory(status, 10, 100,
|
| - debugInfo)
|
| -
|
| - allChanges.extend(self.initialRevs)
|
| -
|
| - # the new changes are not sorted, and can contain duplicates.
|
| - # Sort the list.
|
| - allChanges.sort(lambda a, b: cmp(getattr(a, self.comparator.getSortingKey()), getattr(b, self.comparator.getSortingKey())))
|
| -
|
| - # Remove the dups
|
| - prevChange = None
|
| - newChanges = []
|
| - for change in allChanges:
|
| - rev = change.revision
|
| - if not prevChange or rev != prevChange.revision:
|
| - newChanges.append(change)
|
| - prevChange = change
|
| - allChanges = newChanges
|
| -
|
| - return allChanges
|
| -
|
| - def stripRevisions(self, allChanges, numRevs, branch, devName):
|
| - """Returns a subset of changesn from allChanges that matches the query.
|
| -
|
| - allChanges is the list of all changes we know about.
|
| - numRevs is the number of changes we will inspect from allChanges. We
|
| - do not want to inspect all of them or it would be too slow.
|
| - branch is the branch we are interested in. Changes not in this branch
|
| - will be ignored.
|
| - devName is the committer username. Changes that have not been submitted
|
| - by this person will be ignored.
|
| - """
|
| -
|
| - revisions = []
|
| -
|
| - if not allChanges:
|
| - return revisions
|
| -
|
| - totalRevs = len(allChanges)
|
| - for i in range(totalRevs-1, totalRevs-numRevs, -1):
|
| - if i < 0:
|
| - break
|
| - change = allChanges[i]
|
| - if branch == ANYBRANCH or branch == change.branch:
|
| - if not devName or change.who in devName:
|
| - rev = DevRevision(change.revision, change.who,
|
| - change.comments, change.getTime(),
|
| - getattr(change, 'revlink', None),
|
| - change.when)
|
| - revisions.append(rev)
|
| -
|
| - return revisions
|
| -
|
| - def getBuildDetails(self, request, builderName, build):
|
| - """Returns an HTML list of failures for a given build."""
|
| - details = ""
|
| - if build.getLogs():
|
| - for step in build.getSteps():
|
| - (result, reason) = step.getResults()
|
| - if result == builder.FAILURE:
|
| - name = step.getName()
|
| -
|
| - # Remove html tags from the error text.
|
| - stripHtml = re.compile(r'<.*?>')
|
| - strippedDetails = stripHtml .sub('', ' '.join(step.getText()))
|
| -
|
| - details += "<li> %s : %s. \n" % (builderName, strippedDetails)
|
| - if step.getLogs():
|
| - details += "[ "
|
| - for log in step.getLogs():
|
| - logname = log.getName()
|
| - logurl = request.childLink(
|
| - "../builders/%s/builds/%s/steps/%s/logs/%s" %
|
| - (urllib.quote(builderName),
|
| - build.getNumber(),
|
| - urllib.quote(name),
|
| - urllib.quote(logname)))
|
| - details += "<a href=\"%s\">%s</a> " % (logurl,
|
| - log.getName())
|
| - details += "]"
|
| - return details
|
| -
|
| - def getBuildsForRevision(self, request, builder, builderName, lastRevision,
|
| - numBuilds, debugInfo):
|
| - """Return the list of all the builds for a given builder that we will
|
| - need to be able to display the console page. We start by the most recent
|
| - build, and we go down until we find a build that was built prior to the
|
| - last change we are interested in."""
|
| -
|
| - revision = lastRevision
|
| - cachedRevision = self.cache.getRevision(builderName)
|
| - if cachedRevision and cachedRevision > lastRevision:
|
| - revision = cachedRevision
|
| -
|
| - builds = []
|
| - build = self.getHeadBuild(builder)
|
| - number = 0
|
| - while build and number < numBuilds:
|
| - debugInfo["builds_scanned"] += 1
|
| - number += 1
|
| -
|
| - # Get the last revision in this build.
|
| - # We first try "got_revision", but if it does not work, then
|
| - # we try "revision".
|
| - got_rev = -1
|
| - try:
|
| - got_rev = build.getProperty("got_revision")
|
| - if not self.comparator.isValidRevision(got_rev):
|
| - got_rev = -1
|
| - except KeyError:
|
| - pass
|
| -
|
| - try:
|
| - if got_rev == -1:
|
| - got_rev = build.getProperty("revision")
|
| - if not self.comparator.isValidRevision(got_rev):
|
| - got_rev = -1
|
| - except:
|
| - pass
|
| -
|
| - # We ignore all builds that don't have last revisions.
|
| - # TODO(nsylvain): If the build is over, maybe it was a problem
|
| - # with the update source step. We need to find a way to tell the
|
| - # user that his change might have broken the source update.
|
| - if got_rev and got_rev != -1:
|
| - details = self.getBuildDetails(request, builderName, build)
|
| - devBuild = DevBuild(got_rev, build.getResults(),
|
| - getInProgressResults(build),
|
| - build.getNumber(),
|
| - build.isFinished(),
|
| - build.getText(),
|
| - build.getETA(),
|
| - details,
|
| - build.getTimes()[0])
|
| -
|
| - builds.append(devBuild)
|
| -
|
| - # Now break if we have enough builds.
|
| - if self.comparator.getSortingKey() == "when":
|
| - current_revision = self.getChangeForBuild(
|
| - builder.getBuild(-1), revision)
|
| - if self.comparator.isRevisionEarlier(
|
| - devBuild, current_revision):
|
| - break
|
| - else:
|
| - if int(got_rev) < int(revision):
|
| - break;
|
| -
|
| -
|
| - build = build.getPreviousBuild()
|
| -
|
| - return builds
|
| -
|
| - def getChangeForBuild(self, build, revision):
|
| - if not build.getChanges(): # Forced build
|
| - devBuild = DevBuild(revision, build.getResults(),
|
| - None,
|
| - build.getNumber(),
|
| - build.isFinished(),
|
| - build.getText(),
|
| - build.getETA(),
|
| - None,
|
| - build.getTimes()[0])
|
| -
|
| - return devBuild
|
| -
|
| - for change in build.getChanges():
|
| - if change.revision == revision:
|
| - return change
|
| -
|
| - # No matching change, return the last change in build.
|
| - changes = list(build.getChanges())
|
| - changes.sort(lambda a, b: cmp(getattr(a, self.comparator.getSortingKey()), getattr(b, self.comparator.getSortingKey())))
|
| - return changes[-1]
|
| -
|
| - def getAllBuildsForRevision(self, status, request, lastRevision, numBuilds,
|
| - categories, builders, debugInfo):
|
| - """Returns a dictionnary of builds we need to inspect to be able to
|
| - display the console page. The key is the builder name, and the value is
|
| - an array of build we care about. We also returns a dictionnary of
|
| - builders we care about. The key is it's category.
|
| -
|
| - lastRevision is the last revision we want to display in the page.
|
| - categories is a list of categories to display. It is coming from the
|
| - HTTP GET parameters.
|
| - builders is a list of builders to display. It is coming from the HTTP
|
| - GET parameters.
|
| - """
|
| -
|
| - allBuilds = dict()
|
| -
|
| - # List of all builders in the dictionnary.
|
| - builderList = dict()
|
| -
|
| - debugInfo["builds_scanned"] = 0
|
| - # Get all the builders.
|
| - builderNames = status.getBuilderNames()[:]
|
| - for builderName in builderNames:
|
| - builder = status.getBuilder(builderName)
|
| -
|
| - # Make sure we are interested in this builder.
|
| - if categories and builder.category not in categories:
|
| - continue
|
| - if builders and builderName not in builders:
|
| - continue
|
| -
|
| - # We want to display this builder.
|
| - category = builder.category or "default"
|
| - # Strip the category to keep only the text before the first |.
|
| - # This is a hack to support the chromium usecase where they have
|
| - # multiple categories for each slave. We use only the first one.
|
| - # TODO(nsylvain): Create another way to specify "display category"
|
| - # in master.cfg.
|
| - category = category.split('|')[0]
|
| - if not builderList.get(category):
|
| - builderList[category] = []
|
| -
|
| - # Append this builder to the dictionnary of builders.
|
| - builderList[category].append(builderName)
|
| - # Set the list of builds for this builder.
|
| - allBuilds[builderName] = self.getBuildsForRevision(request,
|
| - builder,
|
| - builderName,
|
| - lastRevision,
|
| - numBuilds,
|
| - debugInfo)
|
| -
|
| - return (builderList, allBuilds)
|
| -
|
| -
|
| - ##
|
| - ## Display functions
|
| - ##
|
| -
|
| - def displayCategories(self, builderList, debugInfo, subs):
|
| - """Display the top category line."""
|
| -
|
| - data = res.main_line_category_header.substitute(subs)
|
| - count = 0
|
| - for category in builderList:
|
| - count += len(builderList[category])
|
| -
|
| - i = 0
|
| - categories = builderList.keys()
|
| - categories.sort()
|
| - for category in categories:
|
| - # First, we add a flag to say if it's the first or the last one.
|
| - # This is useful is your css is doing rounding at the edge of the
|
| - # tables.
|
| - subs["first"] = ""
|
| - subs["last"] = ""
|
| - if i == 0:
|
| - subs["first"] = "first"
|
| - if i == len(builderList) -1:
|
| - subs["last"] = "last"
|
| -
|
| - # TODO(nsylvain): Another hack to display the category in a pretty
|
| - # way. If the master owner wants to display the categories in a
|
| - # given order, he/she can prepend a number to it. This number won't
|
| - # be shown.
|
| - subs["category"] = category.lstrip('0123456789')
|
| -
|
| - # To be able to align the table correctly, we need to know
|
| - # what percentage of space this category will be taking. This is
|
| - # (#Builders in Category) / (#Builders Total) * 100.
|
| - subs["size"] = (len(builderList[category]) * 100) / count
|
| - data += res.main_line_category_name.substitute(subs)
|
| - i += 1
|
| - data += res.main_line_category_footer.substitute(subs)
|
| - return data
|
| -
|
| - def displaySlaveLine(self, status, builderList, debugInfo, subs, jsonFormat=False):
|
| - """Display a line the shows the current status for all the builders we
|
| - care about."""
|
| -
|
| - data = ""
|
| - json = ""
|
| -
|
| - # Display the first TD (empty) element.
|
| - subs["last"] = ""
|
| - if len(builderList) == 1:
|
| - subs["last"] = "last"
|
| - data += res.main_line_slave_header.substitute(subs)
|
| -
|
| - nbSlaves = 0
|
| - subs["first"] = ""
|
| -
|
| - # Get the number of builders.
|
| - for category in builderList:
|
| - nbSlaves += len(builderList[category])
|
| -
|
| - i = 0
|
| -
|
| - # Get the catefories, and order them alphabetically.
|
| - categories = builderList.keys()
|
| - categories.sort()
|
| - json += '['
|
| -
|
| - # For each category, we display each builder.
|
| - for category in categories:
|
| - subs["last"] = ""
|
| -
|
| - # If it's the last category, we set the "last" flag.
|
| - if i == len(builderList) - 1:
|
| - subs["last"] = "last"
|
| -
|
| - # This is not the first category, we need to add the spacing we have
|
| - # between 2 categories.
|
| - if i != 0:
|
| - data += res.main_line_slave_section.substitute(subs)
|
| -
|
| - i += 1
|
| -
|
| - # For each builder in this category, we set the build info and we
|
| - # display the box.
|
| - for builder in builderList[category]:
|
| - subs["color"] = "notstarted"
|
| - subs["title"] = builder
|
| - subs["url"] = "./builders/%s" % urllib.quote(builder)
|
| - state, builds = status.getBuilder(builder).getState()
|
| - # Check if it's offline, if so, the box is purple.
|
| - if state == "offline":
|
| - subs["color"] = "exception"
|
| - else:
|
| - # If not offline, then display the result of the last
|
| - # finished build.
|
| - build = self.getHeadBuild(status.getBuilder(builder))
|
| - while build and not build.isFinished():
|
| - build = build.getPreviousBuild()
|
| -
|
| - if build:
|
| - subs["color"] = getResultsClass(build.getResults(), None,
|
| - False)
|
| -
|
| - json += ("{'url': '%s', 'title': '%s', 'color': '%s',"
|
| - " 'name': '%s'}," % (subs["url"], subs["title"],
|
| - subs["color"],
|
| - urllib.quote(builder)))
|
| -
|
| - data += res.main_line_slave_status.substitute(subs)
|
| -
|
| - json += ']'
|
| - data += res.main_line_slave_footer.substitute(subs)
|
| -
|
| - if jsonFormat:
|
| - return json
|
| - return data
|
| -
|
| - def displayStatusLine(self, builderList, allBuilds, revision, tempCache,
|
| - debugInfo, subs, jsonFormat=False):
|
| - """Display the boxes that represent the status of each builder in the
|
| - first build "revision" was in. Returns an HTML list of errors that
|
| - happened during these builds."""
|
| -
|
| - data = ""
|
| - json = ""
|
| -
|
| - # Display the first TD (empty) element.
|
| - subs["last"] = ""
|
| - if len(builderList) == 1:
|
| - subs["last"] = "last"
|
| - data += res.main_line_status_header.substitute(subs)
|
| -
|
| - details = ""
|
| - nbSlaves = 0
|
| - subs["first"] = ""
|
| - for category in builderList:
|
| - nbSlaves += len(builderList[category])
|
| -
|
| - i = 0
|
| - # Sort the categories.
|
| - categories = builderList.keys()
|
| - categories.sort()
|
| - json += '['
|
| -
|
| - # Display the boxes by category group.
|
| - for category in categories:
|
| - # Last category? We set the "last" flag.
|
| - subs["last"] = ""
|
| - if i == len(builderList) - 1:
|
| - subs["last"] = "last"
|
| -
|
| - # Not the first category? We add the spacing between 2 categories.
|
| - if i != 0:
|
| - data += res.main_line_status_section.substitute(subs)
|
| - i += 1
|
| -
|
| - # Display the boxes for each builder in this category.
|
| - for builder in builderList[category]:
|
| - introducedIn = None
|
| - firstNotIn = None
|
| -
|
| - cached_value = self.cache.get(builder, revision.revision)
|
| - if cached_value:
|
| - debugInfo["from_cache"] += 1
|
| - subs["url"] = cached_value.url
|
| - subs["title"] = cached_value.title
|
| - subs["color"] = cached_value.color
|
| - subs["tag"] = cached_value.tag
|
| - data += res.main_line_status_box.substitute(subs)
|
| -
|
| - json += ("{'url': '%s', 'title': '%s', 'color': '%s',"
|
| - " 'name': '%s'}," % (subs["url"], subs["title"],
|
| - subs["color"],
|
| - urllib.quote(builder)))
|
| -
|
| - # If the box is red, we add the explaination in the details
|
| - # section.
|
| - if cached_value.details and cached_value.color == "failure":
|
| - details += cached_value.details
|
| -
|
| - continue
|
| -
|
| -
|
| - # Find the first build that does not include the revision.
|
| - for build in allBuilds[builder]:
|
| - if self.comparator.isRevisionEarlier(build, revision):
|
| - firstNotIn = build
|
| - break
|
| - else:
|
| - introducedIn = build
|
| -
|
| - # Get the results of the first build with the revision, and the
|
| - # first build that does not include the revision.
|
| - results = None
|
| - inProgressResults = None
|
| - previousResults = None
|
| - if introducedIn:
|
| - results = introducedIn.results
|
| - inProgressResults = introducedIn.inProgressResults
|
| - if firstNotIn:
|
| - previousResults = firstNotIn.results
|
| -
|
| - isRunning = False
|
| - if introducedIn and not introducedIn.isFinished:
|
| - isRunning = True
|
| -
|
| - url = "./waterfall"
|
| - title = builder
|
| - tag = ""
|
| - current_details = None
|
| - if introducedIn:
|
| - current_details = introducedIn.details or ""
|
| - url = "./buildstatus?builder=%s&number=%s" % (urllib.quote(builder),
|
| - introducedIn.number)
|
| - title += " "
|
| - title += urllib.quote(' '.join(introducedIn.text), ' \n\\/:')
|
| -
|
| - builderStrip = builder.replace(' ', '')
|
| - builderStrip = builderStrip.replace('(', '')
|
| - builderStrip = builderStrip.replace(')', '')
|
| - builderStrip = builderStrip.replace('.', '')
|
| - tag = "Tag%s%s" % (builderStrip, introducedIn.number)
|
| -
|
| - if isRunning:
|
| - title += ' ETA: %ds' % (introducedIn.eta or 0)
|
| -
|
| - resultsClass = getResultsClass(results, previousResults, isRunning,
|
| - inProgressResults)
|
| - subs["url"] = url
|
| - subs["title"] = title
|
| - subs["color"] = resultsClass
|
| - subs["tag"] = tag
|
| -
|
| - json += ("{'url': '%s', 'title': '%s', 'color': '%s',"
|
| - " 'name': '%s'}," % (url, title, resultsClass,
|
| - urllib.quote(builder)))
|
| - data += res.main_line_status_box.substitute(subs)
|
| -
|
| - # If the box is red, we add the explaination in the details
|
| - # section.
|
| - if current_details and resultsClass == "failure":
|
| - details += current_details
|
| -
|
| - # Add this box to the cache if it's completed so we don't have
|
| - # to compute it again.
|
| - if resultsClass != "running" and \
|
| - resultsClass != "running_failure" and \
|
| - resultsClass != "notstarted":
|
| - debugInfo["added_blocks"] += 1
|
| - self.cache.insert(builder, revision.revision, resultsClass, title,
|
| - current_details, url, tag)
|
| - tempCache.insert(builder, revision.revision)
|
| -
|
| - json += ']'
|
| - data += res.main_line_status_footer.substitute(subs)
|
| -
|
| - if jsonFormat:
|
| - return (json, details)
|
| -
|
| - return (data, details)
|
| -
|
| - def displayPage(self, request, status, builderList, allBuilds, revisions,
|
| - categories, branch, tempCache, debugInfo, jsonFormat=False):
|
| - """Display the console page."""
|
| - # Build the main template directory with all the informations we have.
|
| - subs = dict()
|
| - subs["projectUrl"] = status.getProjectURL() or ""
|
| - subs["projectName"] = status.getProjectName() or ""
|
| - safe_branch = branch
|
| - if safe_branch and safe_branch != ANYBRANCH:
|
| - safe_branch = urllib.quote(safe_branch)
|
| - subs["branch"] = safe_branch or 'trunk'
|
| - if categories:
|
| - subs["categories"] = urllib.quote(' '.join(categories)).replace(
|
| - '%20', ' ')
|
| - subs["welcomeUrl"] = self.path_to_root(request) + "index.html"
|
| - subs["version"] = version
|
| - subs["time"] = time.strftime("%a %d %b %Y %H:%M:%S",
|
| - time.localtime(util.now()))
|
| - subs["debugInfo"] = debugInfo
|
| -
|
| -
|
| - #
|
| - # Show the header.
|
| - #
|
| -
|
| - json = "["
|
| - data = res.top_header.substitute(subs)
|
| - data += res.top_info_name.substitute(subs)
|
| -
|
| - if categories:
|
| - data += res.top_info_categories.substitute(subs)
|
| -
|
| - if branch != ANYBRANCH:
|
| - data += res.top_info_branch.substitute(subs)
|
| -
|
| - data += res.top_info_name_end.substitute(subs)
|
| - # Display the legend.
|
| - data += res.top_legend.substitute(subs)
|
| -
|
| - # Display the personalize box.
|
| - data += res.top_personalize.substitute(subs)
|
| -
|
| - data += res.top_footer.substitute(subs)
|
| -
|
| -
|
| - #
|
| - # Display the main page
|
| - #
|
| - data += res.main_header.substitute(subs)
|
| -
|
| - # "Alt" is set for every other line, to be able to switch the background
|
| - # color.
|
| - subs["alt"] = "Alt"
|
| - subs["first"] = ""
|
| - subs["last"] = ""
|
| -
|
| - # Display the categories if there is more than 1.
|
| - if builderList and len(builderList) > 1:
|
| - dataToAdd = self.displayCategories(builderList, debugInfo, subs)
|
| - data += dataToAdd
|
| -
|
| - # Display the build slaves status.
|
| - if builderList:
|
| - dataToAdd = self.displaySlaveLine(status, builderList, debugInfo,
|
| - subs, jsonFormat)
|
| - data += dataToAdd
|
| - json += dataToAdd + ","
|
| -
|
| - # For each revision we show one line
|
| - for revision in revisions:
|
| - if not subs["alt"]:
|
| - subs["alt"] = "Alt"
|
| - else:
|
| - subs["alt"] = ""
|
| -
|
| - # Fill the dictionnary with these new information
|
| - subs["revision"] = revision.revision
|
| - if revision.revlink:
|
| - subs["revision_link"] = ("<a href=\"%s\">%s</a>"
|
| - % (revision.revlink,
|
| - revision.revision))
|
| - else:
|
| - subs["revision_link"] = revision.revision
|
| - subs["who"] = revision.who
|
| - subs["date"] = revision.date
|
| - comment = revision.comments or ""
|
| - subs["comments"] = comment.replace('<', '<').replace('>', '>')
|
| - # Re-encode to make sure it doesn't throw an encoding error on the
|
| - # server.
|
| - try:
|
| - comment_quoted = urllib.quote(
|
| - subs["comments"].decode("utf-8", "ignore").encode(
|
| - "ascii", "xmlcharrefreplace"))
|
| - except UnicodeEncodeError:
|
| - # TODO(maruel): Figure out what's happening.
|
| - comment_quoted = urllib.quote(subs["comments"].encode("utf-8"))
|
| - json += ( "{'revision': '%s', 'date': '%s', 'comments': '%s',"
|
| - "'results' : " ) % (subs["revision"], subs["date"],
|
| - comment_quoted)
|
| -
|
| - # Display the revision number and the committer.
|
| - data += res.main_line_info.substitute(subs)
|
| -
|
| - # Display the status for all builders.
|
| - (dataToAdd, details) = self.displayStatusLine(builderList,
|
| - allBuilds,
|
| - revision,
|
| - tempCache,
|
| - debugInfo,
|
| - subs,
|
| - jsonFormat)
|
| - data += dataToAdd
|
| - json += dataToAdd + "}"
|
| -
|
| - # Calculate the td span for the comment and the details.
|
| - subs["span"] = len(builderList) + 2
|
| -
|
| - # Display the details of the failures, if any.
|
| - if details:
|
| - subs["details"] = details
|
| - data += res.main_line_details.substitute(subs)
|
| -
|
| - # Display the comments for this revision
|
| - data += res.main_line_comments.substitute(subs)
|
| -
|
| - data += res.main_footer.substitute(subs)
|
| -
|
| - #
|
| - # Display the footer of the page.
|
| - #
|
| - debugInfo["load_time"] = time.time() - debugInfo["load_time"]
|
| - data += res.bottom.substitute(subs)
|
| -
|
| - json += "]"
|
| - if jsonFormat:
|
| - return json
|
| -
|
| - return data
|
| -
|
| - def body(self, request):
|
| - "This method builds the main console view display."
|
| -
|
| - # Debug information to display at the end of the page.
|
| - debugInfo = dict()
|
| - debugInfo["load_time"] = time.time()
|
| -
|
| - # get url parameters
|
| - # Categories to show information for.
|
| - categories = request.args.get("category", [])
|
| - # List of all builders to show on the page.
|
| - builders = request.args.get("builder", [])
|
| - # Branch used to filter the changes shown.
|
| - branch = request.args.get("branch", [ANYBRANCH])[0]
|
| - # List of all the committers name to display on the page.
|
| - devName = request.args.get("name", [])
|
| - # json format.
|
| - jsonFormat = request.args.get("json", [False])[0]
|
| -
|
| -
|
| - # and the data we want to render
|
| - status = self.getStatus(request)
|
| -
|
| - projectURL = status.getProjectURL()
|
| - projectName = status.getProjectName()
|
| -
|
| - # Get all revisions we can find.
|
| - source = self.getChangemaster(request)
|
| - allChanges = self.getAllChanges(source, status, debugInfo)
|
| -
|
| - debugInfo["source_all"] = len(allChanges)
|
| -
|
| - # Keep only the revisions we care about.
|
| - # By default we process the last 40 revisions.
|
| - # If a dev name is passed, we look for the changes by this person in the
|
| - # last 160 revisions.
|
| - numRevs = 40
|
| - if devName:
|
| - numRevs *= 4
|
| - numBuilds = numRevs
|
| -
|
| -
|
| - revisions = self.stripRevisions(allChanges, numRevs, branch, devName)
|
| - debugInfo["revision_final"] = len(revisions)
|
| -
|
| - # Fetch all the builds for all builders until we get the next build
|
| - # after lastRevision.
|
| - builderList = None
|
| - allBuilds = None
|
| - if revisions:
|
| - lastRevision = revisions[len(revisions)-1].revision
|
| - debugInfo["last_revision"] = lastRevision
|
| -
|
| - (builderList, allBuilds) = self.getAllBuildsForRevision(status,
|
| - request,
|
| - lastRevision,
|
| - numBuilds,
|
| - categories,
|
| - builders,
|
| - debugInfo)
|
| -
|
| - tempCache = TemporaryCache()
|
| - debugInfo["added_blocks"] = 0
|
| - debugInfo["from_cache"] = 0
|
| -
|
| - data = ""
|
| -
|
| - if request.args.get("display_cache", None):
|
| - data += "<br>Global Cache"
|
| - data += self.cache.display()
|
| - data += "<br>Temporary Cache"
|
| - data += tempCache.display()
|
| -
|
| - if (jsonFormat and int(jsonFormat) == 1):
|
| - revisions = revisions[0:1]
|
| - data += self.displayPage(request, status, builderList, allBuilds,
|
| - revisions, categories, branch, tempCache,
|
| - debugInfo, jsonFormat)
|
| -
|
| - if not devName and branch == ANYBRANCH and not categories and not jsonFormat:
|
| - tempCache.updateGlobalCache(self.cache)
|
| - self.cache.trim()
|
| -
|
| - return data
|
| -
|
| -class RevisionComparator(object):
|
| - """Used for comparing between revisions, as some
|
| - VCS use a plain counter for revisions (like SVN)
|
| - while others use different concepts (see Git).
|
| - """
|
| -
|
| - # TODO (avivby): Should this be a zope interface?
|
| -
|
| - def isRevisionEarlier(self, first_change, second_change):
|
| - """Used for comparing 2 changes"""
|
| - raise NotImplementedError
|
| -
|
| - def isValidRevision(self, revision):
|
| - """Checks whether the revision seems like a VCS revision"""
|
| - raise NotImplementedError
|
| -
|
| - def getSortingKey(self):
|
| - raise NotImplementedError
|
| -
|
| -class TimeRevisionComparator(RevisionComparator):
|
| - def isRevisionEarlier(self, first, second):
|
| - return first.when < second.when
|
| -
|
| - def isValidRevision(self, revision):
|
| - return True # No general way of determining
|
| -
|
| - def getSortingKey(self):
|
| - return "when"
|
| -
|
| -class IntegerRevisionComparator(RevisionComparator):
|
| - def isRevisionEarlier(self, first, second):
|
| - return int(first.revision) < int(second.revision)
|
| -
|
| - def isValidRevision(self, revision):
|
| - try:
|
| - int(revision)
|
| - return True
|
| - except:
|
| - return False
|
| -
|
| - def getSortingKey(self):
|
| - return "revision"
|
|
|