On Wed, Feb 15, 2017 at 04:53:14PM +0100, Mattia Rizzolo wrote: > (also re-attached the attachment). ... here. -- regards, Mattia Rizzolo GPG Key: 66AE 2B4A FCCF 3F52 DA18 4D18 4B04 3FCD B944 4540 .''`. more about me: https://mapreri.org : :' : Launchpad user: https://launchpad.net/~mapreri `. `'` Debian QA page: https://qa.debian.org/developer.php?login=mattia `-
From 9cf994e38331f895fae15772225925e25d304391 Mon Sep 17 00:00:00 2001 From: Erwan Prioul <erwan@linux.vnet.ibm.com> Date: Wed, 15 Feb 2017 15:43:32 +0100 Subject: [PATCH] add first version of ftbfs.cgi script that displays the FTBFS packages on a given architecture, with related bugs if any. --- web/cgi-bin/ftbfs.cgi | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100755 web/cgi-bin/ftbfs.cgi diff --git a/web/cgi-bin/ftbfs.cgi b/web/cgi-bin/ftbfs.cgi new file mode 100755 index 0000000..b340e59 --- /dev/null +++ b/web/cgi-bin/ftbfs.cgi @@ -0,0 +1,247 @@ +#!/usr/bin/env python +# Display FTBFS packages on given arch +# Copyright (C) 2017, Erwan Prioul <erwan@linux.vnet.ibm.com> +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# You should have received a copy of the GNU General Public License along with +# this program. If not, see <http://www.gnu.org/licenses/>. + +import datetime +import psycopg2 +import cgi +import cgitb + +DATABASE = { + 'database': 'udd', + 'port': 5452, + 'host': 'localhost', + 'user': 'guest' +} + +class AttrDict(dict): + def __init__(self, **kwargs): + for key, value in kwargs.iteritems(): + self[key] = value + + def __getattr__(self, name): + try: + return self[name] + except KeyError, e: + raise AttributeError(e) + +def query(query, cols, *parameters): + try: + conn = psycopg2.connect(**DATABASE) + cursor = conn.cursor() + cursor.execute(query, parameters) + except: + exit(1) + for row in cursor.fetchall(): + yield AttrDict(**dict(zip(cols, row))) + cursor.close() + conn.close() + +def pretty_time_delta(when): + seconds = (datetime.datetime.now() - when).total_seconds() + days, seconds = divmod(seconds, 86400) + hours, seconds = divmod(seconds, 3600) + minutes, seconds = divmod(seconds, 60) + if days > 0: + return '%dd' % (days) + elif hours > 0: + return '%dh' % (hours) + elif minutes > 0: + return '%dm' % (minutes) + else: + return '%ds' % (seconds) + +def packageLine(packages, package, pending = False): + u = package.replace('+', '%2B') + bugs = " " + what = 'nopatch' + if pending: + what = 'patch' + if len(packages[package][what]) > 0: + bugs = ''.join('<a href="http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%d">#%d</a> - %s<br>' % (x[0], x[0], x[1]) for x in sorted(packages[package][what])) + return \ + '<tr><td class="package">%s_%s</td>' % (package, packages[package]['version']) + \ + '<td class="links">' + \ + '<a href="https://buildd.debian.org/status/package.php?p=%s&suite=sid" title="Debian buildd status">%s</a> ' % (u, pretty_time_delta(packages[package]['state_change'])) + \ + '[<a href="http://bugs.debian.org/cgi-bin/pkgreport.cgi?src=%s" title="Debian bugs in source package">B</a>]' % (u) + \ + '[<a href="http://buildd.debian.org/status/logs.php?pkg=%s" title="Debian build logs">L</a>]' % (u) + \ + '[<a href="https://tracker.debian.org/pkg/%s" title="Debian Package Tracker">T</a>]</td>' % (u) + \ + '<td>%s</td></tr>' % bugs + +def generatePending(packages, nb, arch): + return \ + '<div class="claim" style="margin-top:20px;">%d FTBFS packages on <span class="arch">%s</span> with a patch pending</div><table>' % (nb, arch) + \ + ''.join(packageLine(packages, x, True) for x in sorted(packages.keys()) if len(packages[x]['patch']) > 0) + \ + '</table>' + +def generateFailing(packages, failing, nb, arch): + html = '<div class="claim">%d FTBFS packages on <span class="arch">%s</span> without patch</div>' % (nb, arch) + for c in sorted(failing.keys()): + if c > 2: + txt = "and %d other architectures" % (c - 1) + elif c == 2: + txt = "and 1 other architecture" + else: + txt = "" + html += \ + '<div class="banner">%d packages are failing on <span class="arch">%s</span> %s</div><table>' % (len(failing[c]), arch, txt) + \ + ''.join(packageLine(packages, x) for x in sorted(failing[c])) + \ + '</table>' + return html + +def getPackages(arch): + # Get all FTBFS packages on given architecture + q = """ +select distinct source, version, state_change +from wannabuild +where architecture = '%s' and + distribution = 'sid' and + state in ('Failed', 'Build-Attempted') and + vancouvered = 'f' +; +""" % arch + packages = {x['s']: { 'version': x['version'], 'state_change': x['state_change'], 'patch': [], 'nopatch': [] } for x in query(q, ('s', 'version', 'state_change'))} + # Get opened bugs with the given architecture as tag + q = """ +select distinct source, id, title +from bugs inner join bugs_usertags using (id) +where bugs_usertags.tag = '%s' and status != 'done' and source in (%s); +""" % (arch, ", ".join("'"+x+"'" for x in packages.keys())) + bugs = {x['id']: [x['source'], x['title']] for x in query(q, ('source', 'id', 'title'))} + # Which bugs have a patch? + q = """ +select id +from bugs_tags +where tag = 'patch' and id in (%s); +""" % ", ".join("'"+str(x)+"'" for x in bugs.keys()) + patched = [x['i'] for x in query(q, ('id'))] + for b in bugs.keys(): + where = 'nopatch' + if b in patched: + where = 'patch' + packages[bugs[b][0]][where].append([b, bugs[b][1]]) + l = [x for x in packages.keys() if len(packages[x]['patch']) <= 0] + countFailing = len(l) + countPending = len(packages.keys()) - countFailing + # For a FTBFS package, get the number of architectures it also fails on + q = """ +select source, count(*) as c +from wannabuild +where distribution = 'sid' and state in ('Failed', 'Build-Attempted') and vancouvered = 'f' and source in (%s) +group by source; +""" % ", ".join("'"+x+"'" for x in l) + failing = {} + for r in query(q, ('source', 'c')): + if not failing.has_key(r['c']): + failing[r['c']] = [] + failing[r['c']].append(r['source']) + return """ +<!doctype html> +<html> +<head> +<meta charset="UTF-8"> +<title>FTBFS packages on %s</title> +<style> +table { + width: 100%%; + border-collapse: collapse; + table-layout: fixed; +} +tr:nth-child(2n+1) { + background-color: #fbfbfb; +} +td { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis !important; + vertical-align: top; + padding-left: 4px; +} +.package { + width: 375px; +} +.links { + width: 150px; + text-align: right; +} +.claim { + background-color: #dddddd; + padding: 10px; + padding-left: 4px !important; + font-size: 110%%; +} +.banner { + background-color: #eeeeee; + padding: 4px; +} +.arch { + font-style: oblique; +} +</style> +</head> +<body> +""" % arch + generateFailing(packages, failing, countFailing, arch) + generatePending(packages, countPending, arch) + '</body></html>' + +def getArchs(): + q = """ +select distinct(architecture) as a +from wannabuild +where distribution = 'sid' and + state in ('Failed', 'Build-Attempted') and + vancouvered = 'f' +order by a; +""" + return [x['a'] for x in query(q, ('a'))] + +def getForm(archs): + return """ +<!doctype html> +<html> +<head> +<title>FTBFS packages on given architecture</title> +<style> +.claim { + background-color: #dddddd; + padding: 10px; + padding-left: 4px !important; + font-size: 110%%; + margin-bottom: 5px; +} +</style> +</head> +<body> +<div class="claim">FTBFS packages</div> +<form action="?" accept-charset="UTF-8"> +Architecture: <select name="arch" id="arch"><option value=""></option>%s</select> <input type="submit" value="Show packages"> +</form> +</body> +</html> +""" % ''.join('<option value="%s">%s</option>' % (x, x) for x in archs) + +def main(): + print 'Content-Type: text/html\n' + archs = getArchs() + cgitb.enable() + form = cgi.FieldStorage() + if 'arch' in form: + arch = form.getfirst('arch', '') + if arch in archs: + print getPackages(arch) + else: + print getForm(archs) + else: + print getForm(archs) + +if __name__ == '__main__': + main() -- 2.7.4
Attachment:
signature.asc
Description: PGP signature