#!/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))