#!/usr/bin/python3 # Copyright 2014 David Steele (dsteele@gmail.com) # # This file is part of Piuparts # # Piuparts 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 2 of the License, or (at your # option) any later version. # # Piuparts 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 . # Piuparts summary generation module # # This module is used to create exportable section and global package testing # result summaries. # # The canonical location for section summaries is at # # https://piuparts.debian.org/
/summary.json # # The global summary is at # # https://piuparts.debian.org/summary.json # # Example output: # # summary.json # { # "_comment": "Debian Piuparts Package Results - https://piuparts.debian.org/...", # "_date": "Wed Feb 26 01:48:43 UTC 2014", # "_id": "Piuparts Package Test Results Summary", # "_type": "source", # "_version": "1.0", # "packages": { # "0ad": { # "overall": [ # "X", # 0, # "http://localhost/piuparts/sid-fail-broken-symlinks/source/0/0ad.html" # ], # "stable": [ # "P", # 0, # "http://localhost/piuparts/wheezy/source/0/0ad.html" # ], # "unstable": [ # "X", # 0, # "http://localhost/piuparts/sid-fail-broken-symlinks/source/0/0ad.html" # ] # }, # "0ad-data": { # ... # } # # # The packages are listed by source package. E.g. "unstable" here is a # json-section (see README_server.txt). The single character flags are # defined below. The number is the number of packages which # are blocked from testing due to a failed package. The URL is a human # friendly page for inspecting the results for that package/distribution. # # Binary package results are combined into source package results. The 'worst' # flag in the group is reported ("F" is worst overall). # # For the global summary, the packages 'worst' result across json-sections # is used. In the case of a tie, the more-important-precedence # section/json-section result is used. # # The global file also includes an 'overall' json-section, which contains # the 'worst' result across the other json-sections. from __future__ import print_function import datetime import json import os from collections import defaultdict, namedtuple INVALID_URL = "invalid url" class SummaryException(Exception): pass SUMMID = "Piuparts Package Test Results Summary" SUMMVER = "1.0" DEFSEC = "overall" FlagInfo = namedtuple("FlagInfo", ["word", "priority", "states"]) flaginfo = { "F": FlagInfo("Failed", 0, ["failed-testing"]), "X": FlagInfo( "Blocked", 1, [ "cannot-be-tested", "dependency-failed-testing", "dependency-cannot-be-tested", "dependency-does-not-exist", ], ), "W": FlagInfo( "Waiting", 2, [ "waiting-to-be-tested", "waiting-for-dependency-to-be-tested", ], ), "P": FlagInfo( "Passed", 3, [ "essential-required", "successfully-tested", ], ), "-": FlagInfo( "Unknown", 4, [ "does-not-exist", "unknown", ], ), } state2flg = dict([(y, x[0]) for x in flaginfo.items() for y in x[1].states]) def worst_flag(*flags): try: flag = min(*flags, key=lambda x: flaginfo[x].priority) except KeyError: raise SummaryException("Unknown flag in " + flags.__repr__()) return flag def get_flag(state): try: flag = state2flg[state] except KeyError: raise SummaryException("Unknown state - " + state) return flag def new_summary(): cdate_array = datetime.datetime.utcnow().ctime().split() utcdate = " ".join(cdate_array[:-1] + ["UTC"] + [cdate_array[-1]]) # define the packages struct. The default should never be the one added dfltentry = ["-", 0, INVALID_URL] pkgstruct = defaultdict(lambda: defaultdict(lambda: dfltentry)) return { "_id": SUMMID, "_version": SUMMVER, "_date": utcdate, "_comment": "Debian Piuparts Package Results - " "https://salsa.debian.org/debian/piuparts/raw/" "develop/piupartslib/pkgsummary.py", "_type": "source", "packages": pkgstruct, } def add_summary(summary, rep_sec, pkg, flag, block_cnt, url): if flag not in flaginfo or not isinstance(block_cnt, int) or not url.startswith("http"): raise SummaryException("Invalid summary argument") pdict = summary["packages"] [old_flag, old_cnt, old_url] = pdict[pkg][rep_sec] block_cnt = max(block_cnt, old_cnt) if old_flag != worst_flag(old_flag, flag) or old_url == INVALID_URL: pdict[pkg][rep_sec] = [flag, block_cnt, url] else: pdict[pkg][rep_sec] = [old_flag, block_cnt, old_url] return summary def merge_summary(gbl_summ, sec_summ): spdict = sec_summ["packages"] for pkg in spdict: for rep_sec in spdict[pkg]: flag, block_cnt, url = spdict[pkg][rep_sec] add_summary(gbl_summ, rep_sec, pkg, flag, block_cnt, url) add_summary(gbl_summ, DEFSEC, pkg, flag, block_cnt, url) return gbl_summ def tooltip(summary, pkg): """Returns e.g. "Failed in testing and stable, blocking 5 packages".""" tip = "" pkgdict = summary["packages"] if pkg in pkgdict: flag, block_cnt, url = pkgdict[pkg][DEFSEC] sections = [x for x in pkgdict[pkg] if x != DEFSEC] applicable = [x for x in sections if pkgdict[pkg][x][0] == flag] tip = flaginfo[flag].word if len(applicable) > 2: tip += " in " + ", ".join(applicable[:-1]) + " and " + applicable[-1] elif len(applicable) == 2: tip += " in " + " and ".join(applicable) elif len(applicable) == 1: tip += " in " + applicable[0] if block_cnt: tip += ", blocking %d packages" % block_cnt tip += "." return tip def write_summary(summary, fname): with open(fname + ".tmp", "w") as fl: json.dump(summary, fl, sort_keys=True, indent=1) os.rename(fname + ".tmp", fname) def read_summary(fname): with open(fname, "r") as fl: result = json.load(fl) if result["_id"] != SUMMID or result["_version"] != SUMMVER: raise SummaryException("Summary JSON header mismatch") return result if __name__ == "__main__": import sys # read a global summary file and return DDPO info by package summary = read_summary(sys.argv[1]) for pkg in summary["packages"]: flag, blocked, url = summary["packages"][pkg][DEFSEC] print(pkg, flag, url, tooltip(summary, pkg))