[pypy-svn] r60739 - in pypy/build/bot2/pypybuildbot: . test

pedronis at codespeak.net pedronis at codespeak.net
Tue Dec 30 18:13:28 CET 2008


Author: pedronis
Date: Tue Dec 30 18:13:26 2008
New Revision: 60739

Modified:
   pypy/build/bot2/pypybuildbot/summary.py
   pypy/build/bot2/pypybuildbot/test/test_summary.py
Log:
- support multiple pytest log step for a builder
- have the run links go to the build pages as some people requested, from there is possible to go to the various logs
- links to builder drill down to drill page
- on builder drill-down pages show also the elapsed time of pytest runs



Modified: pypy/build/bot2/pypybuildbot/summary.py
==============================================================================
--- pypy/build/bot2/pypybuildbot/summary.py	(original)
+++ pypy/build/bot2/pypybuildbot/summary.py	Tue Dec 30 18:13:26 2008
@@ -1,4 +1,4 @@
-import urllib, time
+import time, urlparse, urllib
 
 import py
 html = py.xml.html
@@ -6,16 +6,33 @@
 from buildbot.status.web.base import HtmlResource
 from buildbot.status.builder import FAILURE, EXCEPTION
 
+def host_agnostic(url):
+    parts = urlparse.urlsplit(url)
+    return urlparse.urlunsplit(('','')+parts[2:])
+
+def show_elapsed(secs):
+    if secs < 5:
+        return "%.02fs" % secs
+    secs = int(round(secs))
+    if secs < 60:
+        return "%ds" % secs
+    if secs < 5*60:
+        return "%dm%d" % (secs/60, secs%60)
+    mins = int(round(secs/60.))
+    if mins < 60:
+        return "%dm" % mins
+    return "%dh%d" % (mins/60, mins%60)
+
 class RevisionOutcomeSet(object):
 
-    def __init__(self, rev, key=None, run_stdio=None):
+    def __init__(self, rev, key=None, run_info=None):
         self.revision = rev
         self.key = key
         self._outcomes = {}
         self.failed = set()
         self.skipped = set()
         self.longreprs = {}
-        self._run_stdio = run_stdio
+        self._run_info = run_info
 
     def populate_one(self, name, shortrepr, longrepr=None):
         if shortrepr == '!':
@@ -68,8 +85,8 @@
     def get_key_namekey(self, namekey):
         return (self.key, namekey)
 
-    def get_run_stdios(self):
-        return {self.key: (self, self._run_stdio)}
+    def get_run_infos(self):
+        return {self.key: (self, self._run_info)}
 
 
 class RevisionOutcomeSetCache(object):
@@ -96,40 +113,42 @@
         builderName, buildNumber = key
         builderStatus = status.getBuilder(builderName)
         build = builderStatus.getBuild(buildNumber)
+        run_url = status.getURLForThing(build)
 
         rev = int(build.getProperty("got_revision"))
-        pytest_log = None
-        stdio_log = None
+        pytest_logs = []
+        pytest_elapsed = 0
         failure = None
-        aborted = False
         for step in build.getSteps():
             logs = dict((log.getName(), log) for log in step.getLogs())
             if 'pytestLog' in logs:
-                stdio_log = logs['stdio']
-                if 'aborted' in step.getText():
-                    aborted = True
-                pytest_log = logs['pytestLog']
-                break
-            elif (stdio_log is None and
+                aborted = 'aborted' in step.getText()
+                pytest_logs.append((step.getName(), logs['pytestLog'], aborted))
+                ts = step.getTimes()
+                if ts[0] is not None and ts[1] is not None:
+                    pytest_elapsed += ts[1]-ts[0]
+            elif (failure is None and
                   step.getResults()[0] in (FAILURE, EXCEPTION)):
                 failure = ' '.join(step.getText())
-                stdio_log = logs.get('stdio')
 
-        if stdio_log is None:
-            stdio_url = "no_log"
-        else:
-            stdio_url = status.getURLForThing(stdio_log)
-            # builbot is broken in this :(
-            stdio_url = stdio_url[:-1]+"stdio"
-            
-        outcome_set = RevisionOutcomeSet(rev, key, stdio_url) 
-        if pytest_log is None or not pytest_log.hasContents():
-            name = failure or '<run>'
-            outcome_set.populate_one(name, '!', "no log from the test run")
-        else:
-            if aborted:
-                outcome_set.populate_one('<run> aborted', '!', "")
-            outcome_set.populate(pytest_log)
+        run_info = {'URL': run_url, 'elapsed': pytest_elapsed or None}
+        outcome_set = RevisionOutcomeSet(rev, key, run_info)
+        someresult = False
+        if pytest_logs:
+            for stepName, resultLog, aborted in pytest_logs:
+                if resultLog.hasContents():
+                    someresult = True
+                    if aborted:
+                        outcome_set.populate_one(stepName+' aborted', '!', "")
+                    outcome_set.populate(resultLog)
+
+        if not someresult:
+            if failure:
+                name = '"%s"' % failure # quote
+            else:
+                name = '<run>'
+            outcome_set.populate_one(name, '!', "no logs from the test run")
+
         return outcome_set
         
     def get(self, status, key):
@@ -203,10 +222,10 @@
     def get_key_namekey(self, namekey):
         return self.map[namekey[0]].get_key_namekey(namekey[1:])
 
-    def get_run_stdios(self):
+    def get_run_infos(self):
         all = {}
         for outcome_set in self.map.itervalues():
-            all.update(outcome_set.get_run_stdios())
+            all.update(outcome_set.get_run_infos())
         return all
          
 # ________________________________________________________________
@@ -228,10 +247,11 @@
 
 class SummaryPage(object):
 
-    def __init__(self):
+    def __init__(self, status):
         self.sections = []
         self.cur_branch=None
         self.fixed_builder = False
+        self.status = status
 
     def make_longrepr_url_for(self, outcome_set, namekey):
         cachekey, namekey = outcome_set.get_key_namekey(namekey)
@@ -244,24 +264,39 @@
         qs = urllib.urlencode(parms)
         return "/summary/longrepr?" + qs
 
-    def make_stdio_anchors_for(self, outcome_set):
+    def make_run_anchors_for(self, outcome_set):
         anchors = []
-        stdios = sorted(outcome_set.get_run_stdios().items())
-        for cachekey, (run, url) in stdios:
+        infos = sorted(outcome_set.get_run_infos().items())
+        for cachekey, (run, info) in infos:
             builder = cachekey[0]
             anchors.append('  ')
-            text = "%s [%d, %d F, %d s]" % (builder,
+            timing = ""
+            if self.fixed_builder and info['elapsed'] is not None:
+                timing = " in %s" % show_elapsed(info['elapsed'])
+            text = "%s [%d, %d F, %d s%s]" % (builder,
                                             run.numpassed,
                                             len(run.failed),
-                                            len(run.skipped))
-            anchors.append(html.a(text, href=url))
+                                            len(run.skipped),
+                                            timing)
+            anchors.append(html.a(text, href=host_agnostic(info['URL'])))
         return anchors
 
     def start_branch(self, branch):
         self.cur_branch = branch
-        branch_anchor = html.a(branch, href="/summary?branch=%s" % branch)
+        branch_anchor = html.a(branch, href="/summary?branch=%s" % branch,
+                               class_="failSummary branch")
         self.sections.append(html.h2(branch_anchor))
 
+    def _builder_anchor(self, builder):
+        if self.fixed_builder:
+            url = self.status.getURLForThing(self.status.getBuilder(builder))
+            cls = "builder"
+        else:
+            url = "/summary?builder=%s" % builder
+            cls = "builderquery"
+        return html.a(builder, href=host_agnostic(url),
+                      class_=' '.join(["failSummary", cls]))
+        
     def _builder_num(self, outcome_set):
         return outcome_set.map.values()[0].key
 
@@ -287,9 +322,10 @@
     def add_section(self, outcome_sets):
         if not outcome_sets:
             return
-        labels = sorted(self._label(outcome_set) for outcome_set in outcome_sets)
-        by_label = sorted((self._label(outcome_set), outcome_set) for outcome_set
-                         in outcome_sets)
+        labels = sorted(self._label(outcome_set)
+                        for outcome_set in outcome_sets)
+        by_label = sorted((self._label(outcome_set), outcome_set)
+                          for outcome_set in outcome_sets)
         lines = []
 
         align = 2*len(labels)-1+len(str(labels[-1]))
@@ -300,7 +336,7 @@
             count_skipped = len(outcome_set.skipped)
             line = [bars(), ' ', self._label_anchor(outcome_set)]
             line.append((align-len(line[0]))*" ")
-            line.append(self.make_stdio_anchors_for(outcome_set))
+            line.append(self.make_run_anchors_for(outcome_set))
             line.append('\n')
             lines.append(line)
         lines.append([bars(), "\n"])
@@ -339,7 +375,14 @@
                                                class_="failSummary failed")])
                     else:
                         line.append(" %s" % letter)
-            for width, key in zip(colwidths, failure):
+            # builder
+            builder_width = colwidths[0]
+            builder = failure[0]
+            spacing = ("  %-*s" % (builder_width, 'x'*len(builder))).rstrip('x')
+            builder_anchor = self._builder_anchor(builder)
+            line.append([spacing, builder_anchor])
+            
+            for width, key in zip(colwidths[1:], failure[1:]):
                 line.append("  %-*s" % (width, key))
             lines.append(line)
             lines.append("\n")
@@ -356,7 +399,7 @@
             num = build.getNumber()
             descr = "%s #%d" % (builderName, num)
             url = status.getURLForThing(build)
-            section.append(html.a(descr, href=url))
+            section.append(html.a(descr, href=host_agnostic(url)))
             section.append(html.br())
         self.sections.append(section)
 
@@ -540,7 +583,7 @@
         
         status = self.getStatus(request)
 
-        page = SummaryPage()
+        page = SummaryPage(status)
         #page.sections.append(repr(request.args))
         
         only_branches = request.args.get('branch', None)

Modified: pypy/build/bot2/pypybuildbot/test/test_summary.py
==============================================================================
--- pypy/build/bot2/pypybuildbot/test/test_summary.py	(original)
+++ pypy/build/bot2/pypybuildbot/test/test_summary.py	Tue Dec 30 18:13:26 2008
@@ -225,6 +225,30 @@
 
     assert runs == {99: 199, 98: 198, 97: 197, 96: 196}
 
+def test_show_elapsed():
+    res = summary.show_elapsed(0.25)
+    assert res == "0.25s"
+    res = summary.show_elapsed(1.0)
+    assert res == "1.00s"           
+    res = summary.show_elapsed(1.25)
+    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"            
+    res = summary.show_elapsed(2*60+30)
+    assert res == "2m30"
+    res = summary.show_elapsed(4*60+30)
+    assert res == "4m30"
+    res = summary.show_elapsed(5*60+30)
+    assert res == "6m"
+    res = summary.show_elapsed(61*60)
+    assert res == "1h1"
+    res = summary.show_elapsed(90*60)
+    assert res == "1h30"                
+
 class _BuilderToStatus(object):
 
     def __init__(self, status):
@@ -286,12 +310,16 @@
 
 def add_builds(builder, builds):
     n = getattr(builder, 'nextBuildNumber', 0)
+    t = 1000
     for rev, reslog in builds:
         build = status_builder.BuildStatus(builder, n)
         build.setProperty('got_revision', str(rev), None)
         step = build.addStepWithName('pytest')
         step.logs.extend([FakeLog(step, 'pytestLog', reslog),
                           FakeLog(step, 'stdio')])
+        step.started = t
+        step.finished = t + (n+1)*60
+        t = step.finished + 30
         build.buildFinished()
         builder.addBuildToCache(build)
         n += 1
@@ -322,19 +350,64 @@
         s = summary.Summary()
         res = witness_branches(s)        
         req = FakeRequest([builder])
-        s.body(req)
+        out = s.body(req)
         branches = res()
 
         assert branches == {None: ({}, [build])}
 
+    def test_one_build_no_logs(self):
+        builder = status_builder.BuilderStatus('builder0')
+        build = status_builder.BuildStatus(builder, 0)
+        build.setProperty('got_revision', '50000', None)
+        build.buildFinished()
+        builder.addBuildToCache(build)
+        builder.nextBuildNumber = len(builder.buildCache)
+
+        s = summary.Summary()
+        res = witness_branches(s)        
+        req = FakeRequest([builder])
+        out = s.body(req)
+        branches = res()
+        
+        revs = branches[None][0]
+        assert revs.keys() == [50000]
+
+        assert '&lt;run&gt;' in out
+
+    def test_one_build_no_logs_failure(self):
+        builder = status_builder.BuilderStatus('builder0')
+        build = status_builder.BuildStatus(builder, 0)
+        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)        
+        build.buildFinished()
+        builder.addBuildToCache(build)
+        builder.nextBuildNumber = len(builder.buildCache)
+
+        s = summary.Summary()
+        res = witness_branches(s)        
+        req = FakeRequest([builder])
+        out = s.body(req)
+        branches = res()
+        
+        revs = branches[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 a\n. b")])
+        add_builds(builder, [(60000, "F TEST1\n. b")])
 
         s = summary.Summary()
         res = witness_branches(s)        
         req = FakeRequest([builder])
-        s.body(req)
+        out = s.body(req)
         branches = res()
 
         revs = branches[None][0]
@@ -343,10 +416,12 @@
         assert outcome.revision == 60000
         assert outcome.key == ('builder0', 0)
 
+        assert 'TEST1' in out
+
     def test_two_builds(self):
         builder = status_builder.BuilderStatus('builder0')
-        add_builds(builder, [(60000, "F a\n. b"),
-                             (60001, "F a\n. b")])
+        add_builds(builder, [(60000, "F TEST1\n. b"),
+                             (60001, "F TEST1\n. b")])
 
         s = summary.Summary()
         res = witness_branches(s)        
@@ -369,10 +444,12 @@
 
         assert revs == [60000, 60001]
 
+        assert 'TEST1' in out        
+
     def test_two_builds_samerev(self):
         builder = status_builder.BuilderStatus('builder0')
-        add_builds(builder, [(60000, "F a\n. b"),
-                             (60000, "F a\n. b")])        
+        add_builds(builder, [(60000, "F TEST1\n. b"),
+                             (60000, "F TEST1\n. b")])        
 
         s = summary.Summary()
         res = witness_branches(s)        
@@ -386,16 +463,18 @@
         assert outcome.revision == 60000
         assert outcome.key == ('builder0', 1)
 
+        assert 'TEST1' in out
+
     def test_two_builds_recentrev(self):
         builder = status_builder.BuilderStatus('builder0')
-        add_builds(builder, [(60000, "F a\n. b"),
-                             (60001, "F a\n. b")])
+        add_builds(builder, [(60000, "F TEST1\n. b"),
+                             (60001, "F TEST1\n. b")])
 
         s = summary.Summary()
         res = witness_branches(s)        
         req = FakeRequest([builder])
         req.args = {'recentrev': ['60000']}
-        s.body(req)
+        out = s.body(req)
         branches = res()
 
         revs = branches[None][0]
@@ -404,11 +483,13 @@
         assert outcome.revision == 60000
         assert outcome.key == ('builder0', 0)
 
+        assert 'TEST1' in out
+
     def test_many_builds_query_builder(self):
         builder = status_builder.BuilderStatus('builder0')
-        add_builds(builder, [(60000, "F a\n. b"),
+        add_builds(builder, [(60000, "F TEST1\n. b"),
                              (60000, ". a\n. b"),
-                             (60001, "F a\n. b")])        
+                             (60001, "F TEST1\n. b")])        
 
         s = summary.Summary()
         res = witness_branches(s)        
@@ -437,12 +518,14 @@
                         ('builder0', 1),
                         ('builder0', 2)]
 
+        assert 'TEST1' in out
+
 
     def test_many_builds_query_builder_builds(self):
         builder = status_builder.BuilderStatus('builder0')
-        add_builds(builder, [(60000, "F a\n. b"),
+        add_builds(builder, [(60000, "F TEST1\n. b"),
                              (60000, ". a\n. b"),
-                             (60001, "F a\n. b")])        
+                             (60001, "F TEST1\n. b")])        
 
         s = summary.Summary()
         res = witness_branches(s)        
@@ -467,3 +550,27 @@
 
         assert runs == [('builder0', 0),
                         ('builder0', 2)]
+
+        assert 'TEST1' in out
+
+    def test_many_pytestLogs(self):
+        builder = status_builder.BuilderStatus('builder1')
+        build = status_builder.BuildStatus(builder, 0)
+        build.setProperty('got_revision', '70000', None)
+        step = build.addStepWithName('pytest')
+        step.logs.extend([FakeLog(step, 'pytestLog', "F TEST1")])
+        step2 = build.addStepWithName('pytest2')
+        step2.logs.extend([FakeLog(step, 'pytestLog', ". x\nF TEST2")])
+        step2.setText(["pytest2", "aborted"])
+        build.buildFinished()
+        builder.addBuildToCache(build)
+        builder.nextBuildNumber = 1
+
+        s = summary.Summary()
+        req = FakeRequest([builder])
+        out = s.body(req)
+
+        assert 'TEST1' in out
+        assert 'TEST2' in out
+        assert 'pytest aborted' not in out        
+        assert 'pytest2 aborted' in out



More information about the Pypy-commit mailing list