Add blocking suites to daily summary
Add title and optional script to html header
This commit is contained in:
@@ -28,12 +28,23 @@ JSON. That would allow custom filtering and stuff like that.
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import cgi
|
||||
import collections
|
||||
import json
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
TestMetadata = collections.namedtuple('TestMetadata', [
|
||||
'okay',
|
||||
'unstable',
|
||||
'failed',
|
||||
'skipped',
|
||||
])
|
||||
|
||||
|
||||
def gen_tests(data, prefix, exact_match):
|
||||
"""Creates the HTML for all test cases.
|
||||
|
||||
@@ -43,14 +54,10 @@ def gen_tests(data, prefix, exact_match):
|
||||
exact_match: Only match Jenkins jobs with name equal to prefix.
|
||||
|
||||
Returns:
|
||||
The HTML as a list of elements along with a tuple of the number of
|
||||
passing, unstable, failing, and skipped tests.
|
||||
(html, TestMetadata) for matching tests
|
||||
"""
|
||||
html = ['<ul class="test">']
|
||||
total_okay = 0
|
||||
total_unstable = 0
|
||||
total_failed = 0
|
||||
total_skipped = 0
|
||||
totals = collections.defaultdict(int)
|
||||
for test in sorted(data, key=string.lower):
|
||||
test_html = ['<ul class="suite">']
|
||||
has_test = False
|
||||
@@ -84,27 +91,28 @@ def gen_tests(data, prefix, exact_match):
|
||||
else:
|
||||
status = 'okay'
|
||||
test_html.append('<li class="suite">')
|
||||
test_html.append('<span class="%s">%d/%d</span>' % (status, num_builds - num_failed, num_builds))
|
||||
test_html.append('<span class="time">%.0f%s</span>' % (avg_time, unit))
|
||||
test_html.append('<span class="%s">%d/%d</span>' % (
|
||||
status, num_builds - num_failed, num_builds))
|
||||
test_html.append(
|
||||
'<span class="time">%.0f%s</span>' % (avg_time, unit))
|
||||
test_html.append(suite)
|
||||
test_html.append('</li>')
|
||||
test_html.append('</ul>')
|
||||
if has_failed:
|
||||
status = 'failed'
|
||||
total_failed += 1
|
||||
elif has_unstable:
|
||||
status = 'unstable'
|
||||
total_unstable += 1
|
||||
elif has_test:
|
||||
status = 'okay'
|
||||
total_okay += 1
|
||||
else:
|
||||
status = 'skipped'
|
||||
total_skipped += 1
|
||||
totals[status] += 1
|
||||
html.append('<li class="test %s">' % status)
|
||||
if exact_match and len(test_html) > 2:
|
||||
if not (test_html[2].startswith('<span') and test_html[3].startswith('<span')):
|
||||
raise ValueError("couldn't extract suite results for prepending")
|
||||
if not (test_html[2].startswith('<span') and
|
||||
test_html[3].startswith('<span')):
|
||||
raise ValueError(
|
||||
'couldn\'t extract suite results for prepending')
|
||||
html.extend(test_html[2:4])
|
||||
html.append(test)
|
||||
else:
|
||||
@@ -112,101 +120,166 @@ def gen_tests(data, prefix, exact_match):
|
||||
html.extend(test_html)
|
||||
html.append('</li>')
|
||||
html.append('</ul>')
|
||||
return '\n'.join(html), (total_okay, total_unstable, total_failed, total_skipped)
|
||||
return '\n'.join(html), TestMetadata(
|
||||
totals['okay'], totals['unstable'], totals['failed'], totals['skipped'])
|
||||
|
||||
def html_header():
|
||||
|
||||
def html_header(title, script):
|
||||
"""Return html header items."""
|
||||
html = ['<html>', '<head>']
|
||||
html.append('<link rel="stylesheet" type="text/css" href="style.css" />')
|
||||
html.append('<script src="script.js"></script>')
|
||||
if title:
|
||||
html.append('<title>%s</title>' % cgi.escape(title))
|
||||
if script:
|
||||
html.append('<script src="script.js"></script>')
|
||||
html.append('</head>')
|
||||
html.append('<body>')
|
||||
return html
|
||||
|
||||
|
||||
def gen_html(data, prefix, exact_match=False):
|
||||
"""Creates the HTML for the entire page.
|
||||
|
||||
Args: Same as gen_tests.
|
||||
Returns: Same as gen_tests.
|
||||
Args:
|
||||
Same as gen_tests.
|
||||
Returns:
|
||||
Same as gen_tests.
|
||||
"""
|
||||
tests_html, (okay, unstable, failed, skipped) = gen_tests(data, prefix, exact_match)
|
||||
html = html_header()
|
||||
tests_html, meta = gen_tests(data, prefix, exact_match)
|
||||
if exact_match:
|
||||
html.append('<div id="header">Suite %s' % prefix)
|
||||
elif len(prefix) > 0:
|
||||
html.append('<div id="header">Suites starting with %s:' % prefix)
|
||||
msg = 'Suite %s' % cgi.escape(prefix)
|
||||
elif prefix:
|
||||
msg = 'Suites starting with %s' % cgi.escape(prefix)
|
||||
else:
|
||||
html.append('<div id="header">All suites:')
|
||||
html.append('<span class="total okay" onclick="toggle(\'okay\');">%s</span>' % okay)
|
||||
html.append('<span class="total unstable" onclick="toggle(\'unstable\');">%d</span>' % unstable)
|
||||
html.append('<span class="total failed" onclick="toggle(\'failed\');">%d</span>' % failed)
|
||||
html.append('<span class="total skipped" onclick="toggle(\'skipped\');">%d</span>' % skipped)
|
||||
msg = 'All suites'
|
||||
html = html_header(title=msg, script=True)
|
||||
html.append('<div id="header">%s:' % msg)
|
||||
fmt = '<span class="total %s" onclick="toggle(\'%s\');">%s</span>'
|
||||
html.append(fmt % ('okay', 'okay', meta.okay))
|
||||
html.append(fmt % ('unstable', 'unstable', meta.unstable))
|
||||
html.append(fmt % ('failed', 'failed', meta.failed))
|
||||
html.append(fmt % ('skipped', 'skipped', meta.skipped))
|
||||
html.append('</div>')
|
||||
html.append(tests_html)
|
||||
html.append('</body>')
|
||||
html.append('</html>')
|
||||
return '\n'.join(html), (okay, unstable, failed, skipped)
|
||||
return '\n'.join(html), meta
|
||||
|
||||
|
||||
def gen_metadata_links(suites):
|
||||
"""Write clickable pass, ustabled, failed stats."""
|
||||
html = []
|
||||
for (name, target), (okay, unstable, failed, skipped) in sorted(suites.iteritems()):
|
||||
for (name, target), meta in sorted(suites.iteritems()):
|
||||
html.append('<a class="suite-link" href="%s">' % target)
|
||||
html.append('<span class="total okay">%d</span>' % okay)
|
||||
html.append('<span class="total unstable">%d</span>' % unstable)
|
||||
html.append('<span class="total failed">%d</span>' % failed)
|
||||
html.append('<span class="total okay">%d</span>' % meta.okay)
|
||||
html.append('<span class="total unstable">%d</span>' % meta.unstable)
|
||||
html.append('<span class="total failed">%d</span>' % meta.failed)
|
||||
html.append(name)
|
||||
html.append('</a>')
|
||||
return html
|
||||
|
||||
def main(args):
|
||||
|
||||
def write_html(outdir, path, html):
|
||||
"""Write html to outdir/path."""
|
||||
with open(os.path.join(outdir, path), 'w') as buf:
|
||||
buf.write(html)
|
||||
|
||||
|
||||
def write_metadata(infile, outdir):
|
||||
"""Writes tests-*.html and suite-*.html files.
|
||||
|
||||
Args:
|
||||
infile: the json file created by gen_json.py
|
||||
outdir: a path to write the html files.
|
||||
"""
|
||||
with open(infile) as buf:
|
||||
data = json.load(buf)
|
||||
|
||||
prefix_metadata = {}
|
||||
prefixes = [
|
||||
'kubernetes',
|
||||
'kubernetes-e2e',
|
||||
'kubernetes-soak',
|
||||
'kubernetes-e2e-gce',
|
||||
'kubernetes-e2e-gke',
|
||||
'kubernetes-upgrade',
|
||||
]
|
||||
for prefix in prefixes:
|
||||
path = 'tests-%s.html' % prefix
|
||||
html, metadata = gen_html(data, prefix, False)
|
||||
write_html(outdir, path, html)
|
||||
prefix_metadata[prefix or 'kubernetes', path] = metadata
|
||||
|
||||
suite_metadata = {}
|
||||
suites = set()
|
||||
for suite_names in data.values():
|
||||
suites.update(suite_names.keys())
|
||||
for suite in sorted(suites):
|
||||
path = 'suite-%s.html' % suite
|
||||
html, metadata = gen_html(data, suite, True)
|
||||
write_html(outdir, path, html)
|
||||
suite_metadata[suite, path] = metadata
|
||||
|
||||
blocking = {
|
||||
'kubelet-gce-e2e-ci',
|
||||
'kubernetes-build',
|
||||
'kubernetes-e2e-gce',
|
||||
'kubernetes-e2e-gce-scalability',
|
||||
'kubernetes-e2e-gce-slow',
|
||||
'kubernetes-e2e-gke',
|
||||
'kubernetes-e2e-gke-slow',
|
||||
'kubernetes-kubemark-5-gce',
|
||||
'kubernetes-kubemark-500-gce',
|
||||
'kubernetes-test-go',
|
||||
}
|
||||
blocking_suite_metadata = {
|
||||
k: v for (k, v) in suite_metadata.items() if k[0] in blocking}
|
||||
|
||||
return prefix_metadata, suite_metadata, blocking_suite_metadata
|
||||
|
||||
|
||||
def write_index(outdir, prefixes, suites, blockers):
|
||||
"""Write the index.html with links to each view, including stat summaries.
|
||||
|
||||
Args:
|
||||
outdir: the path to write the index.html file
|
||||
prefixes: the {(prefix, path): TestMetadata} map
|
||||
suites: the {(suite, path): TestMetadata} map
|
||||
blockers: the {(suite, path): TestMetadata} map of blocking suites
|
||||
"""
|
||||
html = html_header(title='Kubernetes Test Summary', script=False)
|
||||
html.append('<h1>Kubernetes Tests</h1>')
|
||||
html.append('Last updated %s' % time.strftime('%F'))
|
||||
|
||||
html.append('<h2>Tests from suites starting with:</h2>')
|
||||
html.extend(gen_metadata_links(prefixes))
|
||||
|
||||
html.append('<h2>Blocking suites:</h2>')
|
||||
html.extend(gen_metadata_links(blockers))
|
||||
|
||||
html.append('<h2>All suites:</h2>')
|
||||
html.extend(gen_metadata_links(suites))
|
||||
|
||||
html.extend(['</body>', '</html>'])
|
||||
write_html(outdir, 'index.html', '\n'.join(html))
|
||||
|
||||
|
||||
def main(infile, outdir):
|
||||
"""Use infile to write test, suite and index html files to outdir."""
|
||||
prefixes, suites, blockers = write_metadata(infile, outdir)
|
||||
write_index(outdir, prefixes, suites, blockers)
|
||||
|
||||
|
||||
def get_options(argv):
|
||||
"""Process command line arguments."""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--suites', action='store_true',
|
||||
help='output test results for each suite')
|
||||
parser.add_argument('--prefixes',
|
||||
help='comma-separated list of suite prefixes to create pages for')
|
||||
parser.add_argument('--output-dir', required=True,
|
||||
help='where to write output pages')
|
||||
parser.add_argument('--input', required=True,
|
||||
help='JSON test data to read for input')
|
||||
options=parser.parse_args(args)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
with open(options.input) as f:
|
||||
data = json.load(f)
|
||||
|
||||
if options.prefixes:
|
||||
# the empty prefix means "all tests"
|
||||
options.prefixes = options.prefixes.split(',')
|
||||
prefix_metadata = {}
|
||||
for prefix in options.prefixes:
|
||||
if prefix:
|
||||
path = 'tests-%s.html' % prefix
|
||||
prefix = 'kubernetes-%s' % prefix
|
||||
else:
|
||||
path = 'tests.html'
|
||||
html, prefix_metadata[prefix or 'kubernetes', path] = gen_html(data, prefix, False)
|
||||
with open(os.path.join(options.output_dir, path), 'w') as f:
|
||||
f.write(html)
|
||||
if options.suites:
|
||||
suites_set = set()
|
||||
for test, suites in data.iteritems():
|
||||
suites_set.update(suites.keys())
|
||||
suite_metadata = {}
|
||||
for suite in sorted(suites_set):
|
||||
path = 'suite-%s.html' % suite
|
||||
html, suite_metadata[suite, path] = gen_html(data, suite, True)
|
||||
with open(os.path.join(options.output_dir, path), 'w') as f:
|
||||
f.write(html)
|
||||
html = html_header()
|
||||
html.append('<h1>Kubernetes Tests</h1>')
|
||||
html.append('Last updated %s' % time.strftime('%F'))
|
||||
if options.prefixes:
|
||||
html.append('<h2>All suites starting with:</h2>')
|
||||
html.extend(gen_metadata_links(prefix_metadata))
|
||||
if options.suites:
|
||||
html.append('<h2>Specific suites:</h2>')
|
||||
html.extend(gen_metadata_links(suite_metadata))
|
||||
html.extend(['</body>', '</html>'])
|
||||
with open(os.path.join(options.output_dir, 'index.html'), 'w') as f:
|
||||
f.write('\n'.join(html))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
OPTIONS = get_options(sys.argv[1:])
|
||||
main(OPTIONS.input, OPTIONS.output_dir)
|
||||
|
||||
Reference in New Issue
Block a user