from sets import Set from nevow import tags from nevow.url import URL from nevow.flat import flatten from buildbot.interfaces import LOG_CHANNEL_STDOUT from buildbot.status.web.base import ICurrentBox, HtmlResource, map_branches, build_get_class from twisted.web.static import Data from twisted.web.error import NoResource from StringIO import StringIO from twisted.spread.jelly import unjelly from twisted.spread.banana import decode class LogMixin: def getLog(self, build, name): for log in build.getLogs(): if log.name.endswith(name): stdout = ''.join( log.getChunks(channels=[LOG_CHANNEL_STDOUT], onlyText=True)) return stdout return '' def body(self, request): status = self.getStatus(request) builderNames = status.getBuilderNames() recentFailures = Set() failedTests = {} anyBuildResults = Set() buildNumbers = range(self.oldBuildCount - 1, -1, -1) for name in builderNames: builder = status.getBuilder(name) recentBuilds = builder.generateFinishedBuilds( branches=[None], num_builds=self.oldBuildCount) for build in recentBuilds: recentFailures.update(self.getLog(build, 'failed').splitlines()) for builderName in builderNames: builder = status.getBuilder(builderName) recentBuilds = builder.generateFinishedBuilds( branches=[None], num_builds=self.oldBuildCount) for buildNumber, build in enumerate(recentBuilds): passed = self.getLog(build, 'passed').splitlines() failed = self.getLog(build, 'failed').splitlines() skipped = self.getLog(build, 'skipped').splitlines() if passed or failed or skipped: anyBuildResults.add(builderName) results = dict.fromkeys( passed, tags.span(style="background-color: green;")['P']) results.update( dict.fromkeys( failed, tags.span(style="background-color: red;")['F'])) results.update( dict.fromkeys( skipped, tags.span(style="background-color: yellow;")['S'])) for testName in recentFailures: key = (builderName, buildNumber, testName) failedTests[key] = results.get( testName, tags.span(style="background-color: cyan;")['?']) testResultsTable = tags.table() heading = tags.tr() for buildNumber in buildNumbers: heading[tags.td()[buildNumber]] heading[tags.td()["<- How many builds ago"]] testResultsTable[heading] builderNames = [ name for name in builderNames if name in anyBuildResults] for testName in sorted(recentFailures): row = tags.tr() for buildNumber in buildNumbers: result = [] for builderName in builderNames: key = (builderName, buildNumber, testName) result.append(failedTests[key]) row[tags.td()[result]] row[tags.td()[testName]] testResultsTable[row] legend = tags.div[ tags.div()["P - Passed"], tags.div()["F - Failed"], tags.div()["S - Skipped"], tags.div()["? - No result available"], tags.div()["Table cells correspond to these builders, left to right:"], [tags.div()[name] for name in builderNames]] return flatten(tags.div()[ legend, testResultsTable]) class Summary(HtmlResource, LogMixin): def body(self, request): failedTests = {} status = self.getStatus(request) for builderName in status.getBuilderNames(): builder = status.getBuilder(builderName) for build in builder.generateFinishedBuilds(branches=[None], num_builds=1): log = self.getLog(build, "failedtotal.log") testNames = log.splitlines() for test in testNames: failedTests[test] = builderName return flatten(tags.div[[ tags.div[tags.a(href=str(URL(None, None, pathsegs=['failures'], querysegs=[('buildname', builderName), ('testname', test)]))[1:], style='color: red; font-weight: bold')['F'], ' ', test] for test, builderName in failedTests.iteritems()]]) def getChild(self, path, request): if path != 'failures': return NoResource() status = self.getStatus(request) builder = status.getBuilder(request.args['buildname'][0]) build = builder.generateFinishedBuilds(branches=[None], num_builds=1).next() testname = request.args['testname'][0] log = self.getLog(build, testname) stuff = unjelly(decode(log)) s = StringIO() s.write(testname + '\n\n') for item in stuff.traceback: s.write(str(item)) s.write(item.source.splitlines()[item.relline] + '\n') s.write(stuff.typename + ': ' + stuff.value + '\n') return Data(s.getvalue(), 'text/plain') class RecentlyFailingTests(HtmlResource, LogMixin): oldBuildCount = 10 def body(self, request): # Avoid having to restart the master to manually exercise each code # change by putting all the interesting logic into a free function # and reloading the module before each call to it. Yea, this is # retarded, but this isn't real software anyway. import pypy_status reload(pypy_status) return body(self, request)