X-Git-Url: http://git.samba.org/samba.git/?p=amitay%2Fbuild-farm.git;a=blobdiff_plain;f=buildfarm%2Fweb%2F__init__.py;h=f58cb9a699cb98ee8a3066a93870706c4975cde8;hp=591d1b0f4819a2ecb2c3d939869eb41e24cdd03a;hb=2d615ebd0036bf088b375655ec3f25a8fb14d28a;hpb=44a24a717f0593b008aa3df81272ec20888041c2 diff --git a/buildfarm/web/__init__.py b/buildfarm/web/__init__.py index 591d1b0..f58cb9a 100755 --- a/buildfarm/web/__init__.py +++ b/buildfarm/web/__init__.py @@ -35,12 +35,13 @@ from collections import defaultdict import os from buildfarm import ( - data, hostdb, util, ) -from buildfarm.filecache import ( - CachingBuildFarm, +from buildfarm.build import ( + LogFileMissing, + NoSuchBuildError, + NoTestOutput, ) import cgi @@ -59,6 +60,16 @@ HISTORY_HORIZON = 1000 # this is automatically filled in deadhosts = [] +def select(name, values, default=None): + yield "" + + def get_param(form, param): """get a param from the request, after sanitizing it""" if param not in form: @@ -73,15 +84,6 @@ def get_param(form, param): return result[0] -def build_link(myself, tree, host, compiler, rev, status): - if rev: - opt_rev = ';revision=%s' % rev - else: - opt_rev = '' - return "%s" % ( - myself, host, tree, compiler, opt_rev, status) - - def html_build_status(status): def span(classname, contents): return "%s" % (classname, contents) @@ -100,50 +102,58 @@ def html_build_status(status): else: return span("status failed", stage.result) - ostatus = "" + ostatus = [] if "panic" in status.other_failures: - ostatus += "/"+span("status panic", "PANIC") + ostatus.append(span("status panic", "PANIC")) if "disk full" in status.other_failures: - ostatus += "/"+span("status failed", "disk full") + ostatus.append(span("status failed", "disk full")) if "timeout" in status.other_failures: - ostatus += "/"+span("status failed", "timeout") + ostatus.append(span("status failed", "timeout")) if "inconsistent test result" in status.other_failures: - ostatus += "/"+span("status failed", "unexpected return code") + ostatus.append(span("status failed", "unexpected return code")) bstatus = "/".join([span_status(s) for s in status.stages]) - if bstatus == "": - bstatus = "?" - return bstatus + ostatus + ret = bstatus + if ostatus: + ret += "(%s)" % ",".join(ostatus) + if ret == "": + ret = "?" + return ret + + +def build_uri(myself, build): + return "%s/build/%s" % (myself, build.log_checksum()) + +def build_link(myself, build): + return "%s" % (build_uri(myself, build), html_build_status(build.status())) -def build_status_html(myself, build): - status = html_build_status(build.status()) - return build_link(myself, build.tree, build.host, build.compiler, build.revision, status) +def tree_uri(myself, tree): + return "%s/tree/%s" % (myself, tree.name) -def build_status_vals(status): - """translate a status into a set of int representing status""" - status = util.strip_html(status) - status = status.replace("ok", "0") - status = status.replace("-", "0") - status = status.replace("?", "0.1") - status = status.replace("PANIC", "1") +def tree_link(myself, tree): + """return a link to a particular tree""" + return "%s:%s" % (tree_uri(myself, tree), tree.name, tree.name, tree.branch) - return status.split("/") + +def host_uri(myself, host): + return "%s/host/%s" % (myself, host) + +def host_link(myself, host): + return "%s" % (host_uri(myself, host), host) + + +def revision_uri(myself, revision, tree): + return "%s?function=diff;tree=%s;revision=%s" % (myself, tree, revision) def revision_link(myself, revision, tree): """return a link to a particular revision""" - if revision is None: return "unknown" - - revision = revision.lstrip() - rev_short = revision - if len(revision) == 40: - rev_short = re.sub("(^.{7}).*", "\\1(git)", rev_short) - - return "%s" % (myself, tree, revision, revision, rev_short) + return "%s" % ( + revision_uri(myself, revision, tree), revision, revision[:7]) def subunit_to_buildfarm_result(subunit_result): @@ -160,6 +170,7 @@ def subunit_to_buildfarm_result(subunit_result): else: return "unknown" + def format_subunit_reason(reason): reason = re.sub("^\[\n+(.*?)\n+\]$", "\\1", reason) return "
%s
" % reason @@ -176,7 +187,7 @@ class LogPrettyPrinter(object): status = m.group(3) # handle pretty-printing of static-analysis tools if actionName == 'cc_checker': - output = "".join(print_log_cc_checker(output)) + output = print_log_cc_checker(output) self.indice += 1 return "".join(make_collapsible_html('action', actionName, output, self.indice, status)) @@ -240,11 +251,12 @@ def print_log_pretty(log): def print_log_cc_checker(input): # generate pretty-printed html for static analysis tools + output = "" + # for now, we only handle the IBM Checker's output style if not re.search("^BEAM_VERSION", input): - yield "here" - # yield input - return + return "here" + return input content = "" inEntry = False @@ -258,9 +270,9 @@ def print_log_cc_checker(input): if line.startswith("-- "): # got a new entry if inEntry: - yield "".join(make_collapsible_html('cc_checker', title, content, id, status)) + output += "".join(make_collapsible_html('cc_checker', title, content, id, status)) else: - yield content + output += content # clear maintenance vars (inEntry, content) = (True, "") @@ -272,7 +284,7 @@ def print_log_cc_checker(input): (title, status, id) = ("%s %s" % (m.group(1), m.group(4)), m.group(2), m.group(3)) elif line.startswith("CC_CHECKER STATUS"): if inEntry: - yield "".join(make_collapsible_html('cc_checker', title, content, id, status)) + output += "".join(make_collapsible_html('cc_checker', title, content, id, status)) inEntry = False content = "" @@ -280,7 +292,7 @@ def print_log_cc_checker(input): # not a new entry, so part of the current entry's output content += "%s\n" % line - yield content + output += content # This function does approximately the same as the following, following # commented-out regular expression except that the regex doesn't quite @@ -291,6 +303,7 @@ def print_log_cc_checker(input): # (.*?) # \n{3,} # }{make_collapsible_html('cc_checker', "$1 $4", $5, $3, $2)}exgs + return output def make_collapsible_html(type, title, output, id, status=""): @@ -299,10 +312,10 @@ def make_collapsible_html(type, title, output, id, status=""): :param type: the logical type of it. e.g. "test" or "action" :param title: the title to be displayed """ - if ((status == "" or "failed" == status.lower())): - icon = 'icon_hide_16.png' + if status.lower() in ("", "failed"): + icon = '/icon_hide_16.png' else: - icon = 'icon_unhide_16.png' + icon = '/icon_unhide_16.png' # trim leading and trailing whitespace output = output.strip() @@ -311,84 +324,26 @@ def make_collapsible_html(type, title, output, id, status=""): # in this html yield "
" % (type, status, type, id) yield "" % id - yield "%s" %(id, id, status, icon) - yield "
%s
\n" % (type, title) - yield "
%s
\n" % (type, status, status) - yield "
\n" % (type, id) - if output and len(output): - yield "
%s
\n" % (output) - yield "
\n" - - -def diff_pretty(diff): - """pretty up a diff -u""" - return highlight(diff, DiffLexer(), HtmlFormatter()) + yield "%s" % (id, id, status, icon) + yield "
%s
" % (type, title) + yield "
%s
" % (type, status, status) + yield "
" % (type, id) + if output: + yield "
%s
" % (output,) + yield "
" def web_paths(t, paths): """change the given source paths into links""" if t.scm == "git": + ret = "" for path in paths: - yield " %s" % (GITWEB_BASE, t.repo, t.subdir, path, t.branch, t.branch, path) + ret += " %s" % (GITWEB_BASE, t.repo, t.subdir, path, t.branch, t.branch, path) + return ret else: raise Exception("Unknown scm %s" % t.scm) -def history_row_html(myself, entry, tree, changes): - """show one row of history table""" - msg = cgi.escape(entry.message) - t = time.asctime(time.gmtime(entry.date)) - age = util.dhm_time(time.time()-entry.date) - - t = t.replace(" ", " ") - - yield """ -
-
- %s
- %s ago""" % (t, age) - if entry.revision: - yield " - %s
" % entry.revision - revision_url = "revision=%s" % entry.revision - else: - revision_url = "author=%s" % entry.author - yield """
-
- show diffs -
- download diffs -
-
-
%s
-
-
-
- Author: %s -
""" % ( - myself, tree.name, entry.date, revision_url, - myself, tree.name, entry.date, revision_url, - msg, entry.author) - - (added, modified, removed) = changes - - if modified: - yield "
Modified: " - yield "".join(web_paths(tree, modified)) - yield "
\n" - - if added: - yield "
Added: " - yield "".join(web_paths(tree, added)) - yield "
\n" - - if removed: - yield "
Removed: " - yield "".join(web_paths(tree, removed)) - yield "
\n" - - yield "
\n" - - def history_row_text(entry, tree, changes): """show one row of history table""" msg = cgi.escape(entry.message) @@ -405,15 +360,6 @@ def history_row_text(entry, tree, changes): yield "\n\n%s\n\n\n" % msg -def show_diff(diff, text_html): - if text_html == "html": - diff = cgi.escape(diff) - diff = diff_pretty(diff) - return "
%s
\n" % diff - else: - return "%s\n" % diff - - class BuildFarmPage(object): def __init__(self, buildfarm): @@ -422,16 +368,9 @@ class BuildFarmPage(object): def red_age(self, age): """show an age as a string""" if age > self.buildfarm.OLDAGE: - return "%s" % util.dhm_time(age) + return "%s" % util.dhm_time(age) return util.dhm_time(age) - def tree_link(self, myself, tree): - # return a link to a particular tree - branch = "" - if tree in self.buildfarm.trees: - branch = ":%s" % self.buildfarm.trees[tree].branch - - return "%s%s" % (myself, tree, tree, tree, branch) def render(self, output_type): raise NotImplementedError(self.render) @@ -441,178 +380,168 @@ class ViewBuildPage(BuildFarmPage): def show_oldrevs(self, myself, tree, host, compiler): """show the available old revisions, if any""" - old_rev_builds = self.buildfarm.builds.get_old_revs(tree, host, compiler) + old_builds = self.buildfarm.builds.get_old_builds(tree, host, compiler) - if len(old_rev_builds) == 0: + if not old_builds: return yield "

Older builds:

\n" yield "\n" - yield "\n" + yield "\n" yield "\n" - for build in old_rev_builds: - yield "\n" % ( - revision_link(myself, build.revision, tree), - build_status_html(myself, build)) + for old_build in old_builds: + yield "\n" % ( + revision_link(myself, old_build.revision, tree), + build_link(myself, old_build), + util.dhm_time(old_build.age)) yield "
RevisionStatus
RevisionStatusAge
%s%s
%s%s%s
\n" - def render(self, myself, tree, host, compiler, rev, plain_logs=False): + def render(self, myself, build, plain_logs=False): """view one build in detail""" - build = self.buildfarm.get_build(tree, host, compiler, rev) - try: - (revision, revision_time) = build.revision_details() - except data.MissingRevisionInfo: - revision = None - - status = build_status_html(myself, build) + uname = None + cflags = None + config = None - if rev: - assert re.match("^[0-9a-fA-F]*$", rev) - - f = build.read_log() try: - log = f.read() - finally: - f.close() + f = build.read_log() + try: + log = f.read() + finally: + f.close() + except LogFileMissing: + log = None f = build.read_err() try: err = f.read() finally: f.close() - log = cgi.escape(log) + if log: + log = cgi.escape(log) - m = re.search("(.*)", log) - if m: - uname = m.group(1) - else: - uname = "" - m = re.search("CFLAGS=(.*)", log) - if m: - cflags = m.group(1) - else: - cflags = "" - m = re.search("configure options: (.*)", log) - if m: - config = m.group(1) - else: - config = "" - err = cgi.escape(err) + m = re.search("(.*)", log) + if m: + uname = m.group(1) + m = re.search("CFLAGS=(.*)", log) + if m: + cflags = m.group(1) + m = re.search("configure options: (.*)", log) + if m: + config = m.group(1) - yield '

Host information:

\n' + err = cgi.escape(err) + yield '

Host information:

' - host_web_file = "../web/%s.html" % host + host_web_file = "../web/%s.html" % build.host if os.path.exists(host_web_file): yield util.FileLoad(host_web_file) yield "\n" - yield ("\n" % - (myself, host, tree, compiler, host, self.buildfarm.hostdb.host(host).platform.encode("utf-8"))) - yield "\n" % uname - yield "\n" % self.tree_link(myself, tree) - yield "\n" % revision_link(myself, revision, tree) + yield "\n" %\ + (myself, build.host, build.tree, build.compiler, build.host, self.buildfarm.hostdb[build.host].platform.encode("utf-8")) + if uname is not None: + yield "\n" % uname + yield "\n" % tree_link(myself, self.buildfarm.trees[build.tree]) + yield "\n" % revision_link(myself, build.revision, build.tree) yield "\n" % self.red_age(build.age) - yield "\n" % status - yield "\n" % compiler - yield "\n" % cflags - yield "\n" % config + yield "\n" % build_link(myself, build) + yield "\n" % build.compiler + if cflags is not None: + yield "\n" % cflags + if config is not None: + yield "\n" % config yield "
Host:%s - %s
Uname:%s
Tree:%s
Build Revision:%s
Host:%s - %s
Uname:%s
Tree:%s
Build Revision:%s
Build age:
%s
Status:%s
Compiler:%s
CFLAGS:%s
configure options:%s
Status:%s
Compiler:%s
CFLAGS:%s
configure options:%s
\n" - yield "".join(self.show_oldrevs(myself, tree, host, compiler)) + yield "".join(self.show_oldrevs(myself, build.tree, build.host, build.compiler)) # check the head of the output for our magic string rev_var = "" - if rev: - rev_var = ";revision=%s" % rev + if build.revision: + rev_var = ";revision=%s" % build.revision - yield "
\n" + yield "
" + + yield "

Subunit output

" % build_uri(myself, build) + yield "

Standard output (as plain text), " % build_uri(myself, build) + yield "Standard error (as plain text)" % build_uri(myself, build) + yield "

" if not plain_logs: - yield ("

Switch to the Plain View

" % (myself, host, tree, compiler, rev_var)) + yield "

Switch to the Plain View

" % (myself, build.host, build.tree, build.compiler, rev_var) - yield "
\n" + yield "
" # These can be pretty wide -- perhaps we need to # allow them to wrap in some way? if err == "": yield "

No error log available

\n" else: - yield "

Error log:

\n" + yield "

Error log:

" yield "".join(make_collapsible_html('action', "Error Output", "\n%s" % err, "stderr-0", "errorlog")) - if log == "": - yield "

No build log available

\n" + if log is None: + yield "

No build log available

" else: yield "

Build log:

\n" yield print_log_pretty(log) - yield "

Some of the above icons derived from the Gnome Project's stock icons.

\n" - yield "
\n" + yield "

Some of the above icons derived from the Gnome Project's stock icons.

" + yield "
" else: - yield ("

Switch to the Enhanced View

\n" % (myself, host, tree, compiler, rev_var)) + yield "

Switch to the Enhanced View

" % (myself, build.host, build.tree, build.compiler, rev_var) if err == "": - yield "

No error log available

\n" + yield "

No error log available

" else: yield '

Error log:

\n' - yield '
%s
\n' % err + yield '
%s
' % err if log == "": - yield '

No build log available

\n' + yield '

No build log available

' else: yield '

Build log:

\n' - yield '
%s
\n' % log + yield '
%s
' % log - yield '
\n' + yield '
' class ViewRecentBuildsPage(BuildFarmPage): - def render(self, myself, tree, sort_by): + def render(self, myself, tree, sort_by=None): """Draw the "recent builds" view""" - last_host = "" all_builds = [] + def build_platform(build): + try: + host = self.buildfarm.hostdb[build.host] + except hostdb.NoSuchHost: + return "UNKNOWN" + else: + return host.platform.encode("utf-8") + cmp_funcs = { - "revision": lambda a, b: cmp(a[7], b[7]), - "age": lambda a, b: cmp(a[0], b[0]), - "host": lambda a, b: cmp(a[2], b[2]), - "platform": lambda a, b: cmp(a[1], b[1]), - "compiler": lambda a, b: cmp(a[3], b[3]), - "status": lambda a, b: cmp(a[6], b[6]), + "revision": lambda a, b: cmp(a.revision, b.revision), + "age": lambda a, b: cmp(a.age, b.age), + "host": lambda a, b: cmp(a.host, b.host), + "platform": lambda a, b: cmp(build_platform(a), build_platform(b)), + "compiler": lambda a, b: cmp(a.compiler, b.compiler), + "status": lambda a, b: cmp(a.status(), b.status()), } - assert tree in self.buildfarm.trees, "not a build tree" - assert sort_by in cmp_funcs, "not a valid sort" + if sort_by is None: + sort_by = "age" - for build in self.buildfarm.get_last_builds(tree=tree): - try: - host = self.buildfarm.hostdb.host(build.host) - except hostdb.NoSuchHost: - # Skip, at least for now. - continue - status = build_status_html(myself, build) - try: - (revision, revision_time) = build.revision_details() - except data.MissingRevisionInfo: - pass - else: - all_builds.append([ - build.age, - host.platform.encode("utf-8"), - "%s" - % (myself, host.name, - tree, build.compiler, host.name, - host.name), - build.compiler, tree, status, build.status(), - revision_link(myself, revision, tree), - revision_time]) + if sort_by not in cmp_funcs: + yield "not a valid sort mechanism: %r" % sort_by + return + + all_builds = list(self.buildfarm.get_tree_builds(tree)) all_builds.sort(cmp_funcs[sort_by]) @@ -620,74 +549,74 @@ class ViewRecentBuildsPage(BuildFarmPage): sorturl = "%s?tree=%s;function=Recent+Builds" % (myself, tree) - yield "
\n" - yield "

Recent builds of %s (%s branch %s)

\n" % (tree, t.scm, t.branch) - yield "\n" - yield "\n" - yield "\n" - yield "\n" % sorturl - yield "\n" % sorturl - yield "\n" - yield "\n" % sorturl - yield "\n" % sorturl - yield "\n" % sorturl - yield "\n" % sorturl - yield "\n" + yield "
" + yield "

Recent builds of %s (%s branch %s)

" % (tree, t.scm, t.branch) + yield "
AgeRevisionTreePlatformHostCompilerStatus
" + yield "" + yield "" + yield "" % sorturl + yield "" % sorturl + yield "" + yield "" % sorturl + yield "" % sorturl + yield "" % sorturl + yield "" % sorturl + yield "" for build in all_builds: - yield "\n" - yield "\n" % util.dhm_time(build[0]) - yield "\n" % build[7] - yield "\n" % build[4] - yield "\n" % build[1] - yield "\n" % build[2] - yield "\n" % build[3] - yield "\n" % build[5] - yield "\n" - yield "
AgeRevisionTreePlatformHostCompilerStatus
%s%s%s%s%s%s%s
\n" - yield "
\n" + yield "" + yield "%s" % util.dhm_time(build.age) + yield "%s" % revision_link(myself, build.revision, build.tree) + yield "%s" % build.tree + yield "%s" % build_platform(build) + yield "%s" % host_link(myself, build.host) + yield "%s" % build.compiler + yield "%s" % build_link(myself, build) + yield "" + yield "" + yield "" class ViewHostPage(BuildFarmPage): def _render_build_list_header(self, host): - yield "
\n" - yield "\n" - yield "

%s - %s

\n" % (host, self.buildfarm.hostdb.host(host).platform.encode("utf-8")) - yield "\n" - yield "\n" - yield "\n" + yield "
TargetBuild
Revision
Build
Age
Status
config/build
install/test
Warnings
" + yield "" + yield "" def _render_build_html(self, myself, build): - try: - (revision, revision_time) = build.revision_details() - except data.MissingRevisionInfo: - revision = None warnings = build.err_count() - status = build_status_html(myself, build) - yield "\n" - yield "\n" % (self.tree_link(myself, build.tree), build.compiler) - yield "\n" % revision_link(myself, revision, build.tree) - yield "\n" % self.red_age(build.age) - yield "\n" % status - yield "\n" % warnings - yield "\n" + yield "" + yield "" + yield "" + yield "" + yield "" % build_link(myself, build) + yield "" % warnings + yield "" def render_html(self, myself, *requested_hosts): - yield "
\n" - yield '

Host summary:

\n' - for host in requested_hosts: - builds = list(self.buildfarm.get_host_builds(host)) + yield "
" + yield '

Host summary:

' + for hostname in requested_hosts: + try: + host = self.buildfarm.hostdb[hostname] + except hostdb.NoSuchHost: + deadhosts.append(hostname) + continue + builds = list(self.buildfarm.get_host_builds(hostname)) if len(builds) > 0: yield "".join(self._render_build_list_header(host)) for build in builds: yield "".join(self._render_build_html(myself, build)) - yield "
TargetBuild
Revision
Build
Age
Status
config/build
install/test
Warnings
%s/%s%s
%s
%s
%s
" + tree_link(myself, self.buildfarm.trees[build.tree]) +"/" + build.compiler + "" + revision_link(myself, build.revision, build.tree) + "
" + self.red_age(build.age) + "
%s
%s
\n" - yield "
\n" + yield "" + yield "" else: - deadhosts.append(host) + deadhosts.append(hostname) - yield "\n" + yield "" yield "".join(self.draw_dead_hosts(*deadhosts)) def render_text(self, myself, *requested_hosts): @@ -697,7 +626,7 @@ class ViewHostPage(BuildFarmPage): for host in requested_hosts: # make sure we have some data from it try: - self.buildfarm.hostdb.host(host) + self.buildfarm.hostdb[host] except hostdb.NoSuchHost: continue @@ -719,20 +648,24 @@ class ViewHostPage(BuildFarmPage): if len(deadhosts) == 0: return - yield "
\n" - yield "

Dead Hosts:

\n" - yield "\n" - yield "\n" - yield "\n" + yield "
" + yield "

Dead Hosts:

" + yield "
HostOSMin Age
" + yield "" + yield "" for host in deadhosts: - age_ctime = self.buildfarm.host_age(host) - yield "\n" %\ - (host, self.buildfarm.hostdb.host(host).platform.encode("utf-8"), - util.dhm_time(age_ctime)) + last_build = self.buildfarm.host_last_build(host) + age = time.time() - last_build + try: + platform = self.buildfarm.hostdb[host].platform.encode("utf-8") + except hostdb.NoSuchHost: + platform = "UNKNOWN" + yield "" %\ + (host, platform, util.dhm_time(age)) - yield "
HostOSMin Age
%s%s%s
%s%s%s
\n" - yield "
\n" + yield "" + yield "" class ViewSummaryPage(BuildFarmPage): @@ -745,7 +678,6 @@ class ViewSummaryPage(BuildFarmPage): # set up a variable to store the broken builds table's code, so we can # output when we want broken_table = "" - last_host = "" builds = self.buildfarm.get_last_builds() @@ -760,11 +692,9 @@ class ViewSummaryPage(BuildFarmPage): return (host_count, broken_count, panic_count) def render_text(self, myself): - (host_count, broken_count, panic_count) = self._get_counts() # for the text report, include the current time - t = time.gmtime() - yield "Build status as of %s\n\n" % t + yield "Build status as of %s\n\n" % time.asctime() yield "Build counts:\n" yield "%-12s %-6s %-6s %-6s\n" % ("Tree", "Total", "Broken", "Panic") @@ -772,7 +702,6 @@ class ViewSummaryPage(BuildFarmPage): for tree in sorted(self.buildfarm.trees.keys()): yield "%-12s %-6s %-6s %-6s\n" % (tree, host_count[tree], broken_count[tree], panic_count[tree]) - yield "\n" def render_html(self, myself): @@ -780,39 +709,102 @@ class ViewSummaryPage(BuildFarmPage): (host_count, broken_count, panic_count) = self._get_counts() - yield "
\n" - yield "

Build counts:

\n" - yield "\n" - yield "\n" - yield "\n" + yield "
" + yield "

Build counts:

" + yield "
TreeTotalBrokenPanicTest coverage
" + yield "" + yield "" for tree in sorted(self.buildfarm.trees.keys()): - yield "\n" - yield "\n" % self.tree_link(myself, tree) - yield "\n" % host_count[tree] - yield "\n" % broken_count[tree] + yield "" + yield "" % tree_link(myself, self.buildfarm.trees[tree]) + yield "" % host_count[tree] + yield "" % broken_count[tree] if panic_count[tree]: - yield "\n" % panic_count[tree] + yield "" % panic_count[tree] try: lcov_status = self.buildfarm.lcov_status(tree) - except data.NoSuchBuildError: - yield "\n" + except NoSuchBuildError: + yield "" else: if lcov_status is not None: - yield "\n" % ( + yield "" % ( self.buildfarm.LCOVHOST, tree, lcov_status) else: - yield "\n" - yield "\n" + yield "" + yield "" - yield "
TreeTotalBrokenPanicTest coverage
%s%s%s
%s%s%s\n" + yield "" else: - yield "\n" - yield "%d" + yield "%d%s %%%s %%
\n" + yield "" + yield "
" + + +class HistoryPage(BuildFarmPage): + + def history_row_html(self, myself, entry, tree, changes): + """show one row of history table""" + msg = cgi.escape(entry.message) + t = time.asctime(time.gmtime(entry.date)) + age = util.dhm_time(time.time()-entry.date) + + t = t.replace(" ", " ") + + yield """ +
+
+ %s
+ %s ago""" % (t, age) + if entry.revision: + yield " - %s
" % entry.revision + revision_url = "revision=%s" % entry.revision + else: + revision_url = "author=%s" % entry.author + yield """
+
+ show diffs +
+ download diffs +
+
+
%s
+
+
+
+ Author: %s +
""" % (myself, tree.name, entry.date, revision_url, + myself, tree.name, entry.date, revision_url, + msg, entry.author) + + (added, modified, removed) = changes + + if modified: + yield "
Modified: " + yield web_paths(tree, modified) + yield "
\n" + + if added: + yield "
Added: " + yield web_paths(tree, added) + yield "
\n" + + if removed: + yield "
Removed: " + yield web_paths(tree, removed) + yield "
\n" + + builds = list(self.buildfarm.get_revision_builds(tree.name, entry.revision)) + if builds: + yield "
\n" + yield "Builds: \n" + for build in builds: + yield "%s(%s) " % (build_link(myself, build), host_link(myself, build.host)) + yield "
\n" yield "
\n" -class DiffPage(BuildFarmPage): +class DiffPage(HistoryPage): def render(self, myself, tree, revision): t = self.buildfarm.trees[tree] @@ -823,11 +815,12 @@ class DiffPage(BuildFarmPage): tree, t.branch, revision) yield "

%s

" % title changes = branch.changes_summary(revision) - yield "".join(history_row_html(myself, entry, t, changes)) - yield show_diff(diff, "html") + yield "".join(self.history_row_html(myself, entry, t, changes)) + diff = highlight(diff, DiffLexer(), HtmlFormatter()) + yield "
%s
\n" % diff.encode("utf-8") -class RecentCheckinsPage(BuildFarmPage): +class RecentCheckinsPage(HistoryPage): limit = 40 @@ -840,17 +833,14 @@ class RecentCheckinsPage(BuildFarmPage): for entry in branch.log(limit=HISTORY_HORIZON): m = re_author.match(entry.author) authors[m.group(2)] = m.group(1) - if author in ("ALL", "", m.group(2)): + if author in (None, "ALL", m.group(2)): interesting.append(entry) yield "

Recent checkins for %s (%s branch %s)

\n" % ( tree, t.scm, t.branch) - yield "
\n" - yield "Select Author: \n" - yield "" + yield "" + yield "Select Author: " + yield "".join(select(name="author", values=authors, default=author)) yield "" yield "" % tree yield "" @@ -858,7 +848,7 @@ class RecentCheckinsPage(BuildFarmPage): for entry in interesting[:self.limit]: changes = branch.changes_summary(entry.revision) - yield "".join(history_row_html(myself, entry, t, changes)) + yield "".join(self.history_row_html(myself, entry, t, changes)) yield "\n" @@ -867,24 +857,20 @@ class BuildFarmApp(object): def __init__(self, buildfarm): self.buildfarm = buildfarm - def main_menu(self): + def main_menu(self, tree, host, compiler): """main page""" yield "\n" yield "
\n" - yield "\n" - yield "\n" - yield "\n" + host_dict = {} + for h in self.buildfarm.hostdb.hosts(): + host_dict[h.name] = "%s -- %s" % (h.platform.encode("utf-8"), h.name) + yield "".join(select("host", host_dict, default=host)) + tree_dict = {} + for t in self.buildfarm.trees.values(): + tree_dict[t.name] = "%s:%s" % (t.name, t.branch) + yield "".join(select("tree", tree_dict, default=tree)) + yield "".join(select("compiler", dict(zip(self.buildfarm.compilers, self.buildfarm.compilers)), default=compiler)) yield "
\n" yield "\n" yield "\n" @@ -894,6 +880,32 @@ class BuildFarmApp(object): yield "
\n" yield "
\n" + def html_page(self, form, lines): + yield "\n" + yield " \n" + yield " samba.org build farm\n" + yield " \n" + yield " \n" + yield " \n" + yield " " + yield " " + yield " " + yield " " + yield " " + yield "" + + yield util.FileLoad(os.path.join(webdir, "header2.html")) + + tree = get_param(form, "tree") + host = get_param(form, "host") + compiler = get_param(form, "compiler") + yield "".join(self.main_menu(tree, host, compiler)) + yield util.FileLoad(os.path.join(webdir, "header3.html")) + yield "".join(lines) + yield util.FileLoad(os.path.join(webdir, "footer.html")) + yield "" + yield "" + def __call__(self, environ, start_response): form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ) fn_name = get_param(form, 'function') or '' @@ -908,77 +920,120 @@ class BuildFarmApp(object): (entry, diff) = branch.diff(revision) changes = branch.changes_summary(revision) yield "".join(history_row_text(entry, tree, changes)) - yield show_diff(diff, "text") + yield "%s\n" % diff elif fn_name == 'Text_Summary': start_response('200 OK', [('Content-type', 'text/plain')]) page = ViewSummaryPage(self.buildfarm) yield "".join(page.render_text(myself)) - else: - start_response('200 OK', [('Content-type', 'text/html')]) - - yield "\n" - yield " \n" - yield " samba.org build farm\n" - yield " \n" - yield " \n" - yield " \n" - yield " \n" - yield " \n" - yield " \n" - yield " \n" - yield " \n" - yield "\n" - - yield util.FileLoad(os.path.join(webdir, "header2.html")) - yield "".join(self.main_menu()) - yield util.FileLoad(os.path.join(webdir, "header3.html")) + elif fn_name: + start_response('200 OK', [ + ('Content-type', 'text/html; charset=utf-8')]) + + tree = get_param(form, "tree") + host = get_param(form, "host") + compiler = get_param(form, "compiler") + if fn_name == "View_Build": plain_logs = (get_param(form, "plain") is not None and get_param(form, "plain").lower() in ("yes", "1", "on", "true", "y")) - tree = get_param(form, "tree") - host = get_param(form, "host") - compiler = get_param(form, "compiler") - page = ViewBuildPage(self.buildfarm) - yield "".join(page.render(myself, tree, host, compiler, get_param(form, "revision"), plain_logs)) + revision = get_param(form, "revision") + checksum = get_param(form, "checksum") + try: + build = self.buildfarm.get_build(tree, host, + compiler, revision, checksum=checksum) + except NoSuchBuildError: + yield "No such build: %s on %s with %s, rev %r, checksum %r" % ( + tree, host, compiler, revision, checksum) + else: + page = ViewBuildPage(self.buildfarm) + plain_logs = (get_param(form, "plain") is not None and get_param(form, "plain").lower() in ("yes", "1", "on", "true", "y")) + yield "".join(self.html_page(form, page.render(myself, build, plain_logs))) elif fn_name == "View_Host": page = ViewHostPage(self.buildfarm) - yield "".join(page.render_html(myself, get_param(form, 'host'))) + yield "".join(self.html_page(form, page.render_html(myself, get_param(form, 'host')))) elif fn_name == "Recent_Builds": page = ViewRecentBuildsPage(self.buildfarm) - yield "".join(page.render(myself, get_param(form, "tree"), get_param(form, "sortby") or "revision")) + yield "".join(self.html_page(form, page.render(myself, get_param(form, "tree"), get_param(form, "sortby") or "age"))) elif fn_name == "Recent_Checkins": # validate the tree - tree = get_param(form, "tree") author = get_param(form, 'author') page = RecentCheckinsPage(self.buildfarm) - yield "".join(page.render(myself, tree, author)) + yield "".join(self.html_page(form, page.render(myself, tree, author))) elif fn_name == "diff": - tree = get_param(form, "tree") revision = get_param(form, 'revision') page = DiffPage(self.buildfarm) - yield "".join(page.render(myself, tree, revision)) - elif os.getenv("PATH_INFO") not in (None, "", "/"): - paths = os.getenv("PATH_INFO").split('/') - if paths[1] == "recent": - page = ViewRecentBuildsPage(self.buildfarm) - yield "".join(page.render(myself, paths[2], get_param(form, 'sortby') or 'revision')) - elif paths[1] == "host": - page = ViewHostPage(self.buildfarm) - yield "".join(page.render_html(myself, paths[2])) + yield "".join(self.html_page(form, page.render(myself, tree, revision))) + elif fn_name == "Summary": + page = ViewSummaryPage(self.buildfarm) + yield "".join(self.html_page(form, page.render_html(myself))) else: + yield "Unknown function %s" % fn_name + else: + fn = wsgiref.util.shift_path_info(environ) + if fn == "tree": + start_response('200 OK', [ + ('Content-type', 'text/html; charset=utf-8')]) + tree = wsgiref.util.shift_path_info(environ) + subfn = wsgiref.util.shift_path_info(environ) + if subfn in ("", None, "+recent"): + page = ViewRecentBuildsPage(self.buildfarm) + yield "".join(self.html_page(form, page.render(myself, tree, get_param(form, 'sortby') or 'age'))) + else: + yield "Unknown subfn %s" % subfn + elif fn == "host": + start_response('200 OK', [ + ('Content-type', 'text/html; charset=utf-8')]) + page = ViewHostPage(self.buildfarm) + yield "".join(self.html_page(form, page.render_html(myself, wsgiref.util.shift_path_info(environ)))) + elif fn == "build": + build_checksum = wsgiref.util.shift_path_info(environ) + build = self.buildfarm.builds.get_by_checksum(build_checksum) + page = ViewBuildPage(self.buildfarm) + subfn = wsgiref.util.shift_path_info(environ) + if subfn == "+plain": + start_response('200 OK', [ + ('Content-type', 'text/html; charset=utf-8')]) + yield "".join(page.render(myself, build, True)) + elif subfn == "+subunit": + start_response('200 OK', [ + ('Content-type', 'text/x-subunit; charset=utf-8'), + ('Content-Disposition', 'attachment; filename="%s.%s.%s-%s.subunit"' % (build.tree, build.host, build.compiler, build.revision))]) + try: + yield build.read_subunit().read() + except NoTestOutput: + yield "There was no test output" + elif subfn == "+stdout": + start_response('200 OK', [ + ('Content-type', 'text/plain; charset=utf-8'), + ('Content-Disposition', 'attachment; filename="%s.%s.%s-%s.log"' % (build.tree, build.host, build.compiler, build.revision))]) + yield build.read_log().read() + elif subfn == "+stderr": + start_response('200 OK', [ + ('Content-type', 'text/plain; charset=utf-8'), + ('Content-Disposition', 'attachment; filename="%s.%s.%s-%s.err"' % (build.tree, build.host, build.compiler, build.revision))]) + yield build.read_err().read() + elif subfn in ("", None): + start_response('200 OK', [ + ('Content-type', 'text/html; charset=utf-8')]) + yield "".join(self.html_page(form, page.render(myself, build, False))) + elif fn in ("", None): + start_response('200 OK', [ + ('Content-type', 'text/html; charset=utf-8')]) page = ViewSummaryPage(self.buildfarm) - yield "".join(page.render_html(myself)) - yield util.FileLoad(os.path.join(webdir, "footer.html")) - yield "\n" - yield "\n" + yield "".join(self.html_page(form, page.render_html(myself))) + else: + start_response('404 Page Not Found', [ + ('Content-type', 'text/html; charset=utf-8')]) + yield "Unknown function %s" % fn if __name__ == '__main__': import optparse parser = optparse.OptionParser("[options]") - parser.add_option("--cachedirname", help="Cache directory name", type=str) - parser.add_option("--port", help="Port to listen on [localhost:8000]", default="localhost:8000", type=str) + parser.add_option("--port", help="Port to listen on [localhost:8000]", + default="localhost:8000", type=str) opts, args = parser.parse_args() - buildfarm = CachingBuildFarm(cachedirname=opts.cachedirname) + from buildfarm import BuildFarm + buildfarm = BuildFarm() buildApp = BuildFarmApp(buildfarm) from wsgiref.simple_server import make_server import mimetypes