mirror of
https://github.com/servo/servo.git
synced 2025-06-26 01:54:33 +01:00
310 lines
9.4 KiB
Python
310 lines
9.4 KiB
Python
import argparse
|
|
import json
|
|
import sys
|
|
from cgi import escape
|
|
from collections import defaultdict
|
|
|
|
import types
|
|
|
|
|
|
def html_escape(item, escape_quote=False):
|
|
if isinstance(item, types.StringTypes):
|
|
rv = escape(item)
|
|
if escape_quote:
|
|
rv = rv.replace('"', """)
|
|
return rv
|
|
else:
|
|
return item
|
|
|
|
|
|
class Raw(object):
|
|
"""Simple wrapper around a string to stop it being escaped by html_escape"""
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
|
|
class Node(object):
|
|
"""Node structure used when building HTML"""
|
|
def __init__(self, name, attrs, children):
|
|
#Need list of void elements
|
|
self.name = name
|
|
self.attrs = attrs
|
|
self.children = children
|
|
|
|
def __unicode__(self):
|
|
if self.attrs:
|
|
#Need to escape
|
|
attrs_unicode = " " + " ".join("%s=\"%s\"" % (html_escape(key),
|
|
html_escape(value,
|
|
escape_quote=True))
|
|
for key, value in self.attrs.iteritems())
|
|
else:
|
|
attrs_unicode = ""
|
|
return "<%s%s>%s</%s>\n" % (self.name,
|
|
attrs_unicode,
|
|
"".join(unicode(html_escape(item))
|
|
for item in self.children),
|
|
self.name)
|
|
|
|
def __str__(self):
|
|
return unicode(self).encode("utf8")
|
|
|
|
|
|
class RootNode(object):
|
|
"""Special Node representing the document root"""
|
|
def __init__(self, *children):
|
|
self.children = ["<!DOCTYPE html>"] + list(children)
|
|
|
|
def __unicode__(self):
|
|
return "".join(unicode(item) for item in self.children)
|
|
|
|
def __str__(self):
|
|
return unicode(self).encode("utf8")
|
|
|
|
|
|
def flatten(iterable):
|
|
"""Flatten a list of lists by one level so that
|
|
[1,["abc"], "def",[2, [3]]]
|
|
becomes
|
|
[1, "abc", "def", 2, [3]]"""
|
|
rv = []
|
|
for item in iterable:
|
|
if hasattr(item, "__iter__") and not isinstance(item, types.StringTypes):
|
|
rv.extend(item)
|
|
else:
|
|
rv.append(item)
|
|
return rv
|
|
|
|
|
|
class HTML(object):
|
|
"""Simple HTML templating system. An instance of this class can create
|
|
element nodes by calling methods with the same name as the element,
|
|
passing in children as positional arguments or as a list, and attributes
|
|
as keyword arguments, with _ replacing - and trailing _ for python keywords
|
|
|
|
e.g.
|
|
|
|
h = HTML()
|
|
print h.html(
|
|
html.head(),
|
|
html.body([html.h1("Hello World!")], class_="body-class")
|
|
)
|
|
Would give
|
|
<!DOCTYPE html><html><head></head><body class="body-class"><h1>Hello World!</h1></body></html>"""
|
|
def __getattr__(self, name):
|
|
def make_html(self, *content, **attrs):
|
|
for attr_name in attrs.keys():
|
|
if "_" in attr_name:
|
|
new_name = attr_name.replace("_", "-")
|
|
if new_name.endswith("-"):
|
|
new_name = new_name[:-1]
|
|
attrs[new_name] = attrs.pop(attr_name)
|
|
return Node(name, attrs, flatten(content))
|
|
|
|
method = types.MethodType(make_html, self, HTML)
|
|
setattr(self, name, method)
|
|
return method
|
|
|
|
def __call__(self, *children):
|
|
return RootNode(*flatten(children))
|
|
|
|
|
|
h = HTML()
|
|
|
|
|
|
class TestResult(object):
|
|
"""Simple holder for the results of a single test in a single UA"""
|
|
def __init__(self, test):
|
|
self.test = test
|
|
self.results = {}
|
|
|
|
def __cmp__(self, other):
|
|
return self.test == other.test
|
|
|
|
def __hash__(self):
|
|
return hash(self.test)
|
|
|
|
|
|
def load_data(args):
|
|
"""Load data treating args as a list of UA name, filename pairs"""
|
|
pairs = []
|
|
for i in xrange(0, len(args), 2):
|
|
pairs.append(args[i:i+2])
|
|
|
|
rv = {}
|
|
for UA, filename in pairs:
|
|
with open(filename) as f:
|
|
rv[UA] = json.load(f)
|
|
|
|
return rv
|
|
|
|
|
|
def test_id(id):
|
|
"""Convert a test id in JSON into an immutable object that
|
|
can be used as a dictionary key"""
|
|
if isinstance(id, list):
|
|
return tuple(id)
|
|
else:
|
|
return id
|
|
|
|
|
|
def all_tests(data):
|
|
tests = defaultdict(set)
|
|
for UA, results in data.iteritems():
|
|
for result in results["results"]:
|
|
id = test_id(result["test"])
|
|
tests[id] |= set(subtest["name"] for subtest in result["subtests"])
|
|
return tests
|
|
|
|
|
|
def group_results(data):
|
|
"""Produce a list of UAs and a dictionary mapping specific tests to their
|
|
status in all UAs e.g.
|
|
["UA1", "UA2"], {"test_id":{"harness":{"UA1": (status1, message1),
|
|
"UA2": (status2, message2)},
|
|
"subtests":{"subtest1": "UA1": (status1-1, message1-1),
|
|
"UA2": (status2-1, message2-1)}}}
|
|
Status and message are None if the test didn't run in a particular UA.
|
|
Message is None if the test didn't produce a message"""
|
|
tests = all_tests(data)
|
|
|
|
UAs = data.keys()
|
|
|
|
def result():
|
|
return {
|
|
"harness": dict((UA, (None, None)) for UA in UAs),
|
|
"subtests": None # init this later
|
|
}
|
|
|
|
results_by_test = defaultdict(result)
|
|
|
|
for UA, results in data.iteritems():
|
|
for test_data in results["results"]:
|
|
id = test_id(test_data["test"])
|
|
result = results_by_test[id]
|
|
|
|
if result["subtests"] is None:
|
|
result["subtests"] = dict(
|
|
(name, dict((UA, (None, None)) for UA in UAs)) for name in tests[id]
|
|
)
|
|
|
|
result["harness"][UA] = (test_data["status"], test_data["message"])
|
|
for subtest in test_data["subtests"]:
|
|
result["subtests"][subtest["name"]][UA] = (subtest["status"],
|
|
subtest["message"])
|
|
|
|
return UAs, results_by_test
|
|
|
|
|
|
def status_cell(status, message=None):
|
|
"""Produce a table cell showing the status of a test"""
|
|
status = status if status is not None else "NONE"
|
|
kwargs = {}
|
|
if message:
|
|
kwargs["title"] = message
|
|
status_text = status.title()
|
|
return h.td(status_text, class_="status " + status,
|
|
**kwargs)
|
|
|
|
|
|
def test_link(test_id, subtest=None):
|
|
"""Produce an <a> element linking to a test"""
|
|
if isinstance(test_id, types.StringTypes):
|
|
rv = [h.a(test_id, href=test_id)]
|
|
else:
|
|
rv = [h.a(test_id[0], href=test_id[0]),
|
|
" %s " % test_id[1],
|
|
h.a(test_id[2], href=test_id[2])]
|
|
if subtest is not None:
|
|
rv.append(" [%s]" % subtest)
|
|
return rv
|
|
|
|
|
|
def summary(UAs, results_by_test):
|
|
"""Render the implementation report summary"""
|
|
not_passing = []
|
|
for test, results in results_by_test.iteritems():
|
|
if not any(item[0] in ("PASS", "OK") for item in results["harness"].values()):
|
|
not_passing.append((test, None))
|
|
for subtest_name, subtest_results in results["subtests"].iteritems():
|
|
if not any(item[0] == "PASS" for item in subtest_results.values()):
|
|
not_passing.append((test, subtest_name))
|
|
if not_passing:
|
|
rv = [
|
|
h.p("The following tests failed to pass in all UAs:"),
|
|
h.ul([h.li(test_link(test, subtest))
|
|
for test, subtest in not_passing])
|
|
]
|
|
else:
|
|
rv = "All tests passed in at least one UA"
|
|
return rv
|
|
|
|
|
|
def result_rows(UAs, test, result):
|
|
"""Render the results for each test run"""
|
|
yield h.tr(
|
|
h.td(
|
|
test_link(test),
|
|
rowspan=(1 + len(result["subtests"]))
|
|
),
|
|
h.td(),
|
|
[status_cell(status, message)
|
|
for UA, (status, message) in sorted(result["harness"].items())],
|
|
class_="test"
|
|
)
|
|
|
|
for name, subtest_result in sorted(result["subtests"].iteritems()):
|
|
yield h.tr(
|
|
h.td(name),
|
|
[status_cell(status, message)
|
|
for UA, (status, message) in sorted(subtest_result.items())],
|
|
class_="subtest"
|
|
)
|
|
|
|
|
|
def result_bodies(UAs, results_by_test):
|
|
return [h.tbody(result_rows(UAs, test, result))
|
|
for test, result in sorted(results_by_test.iteritems())]
|
|
|
|
|
|
def generate_html(UAs, results_by_test):
|
|
"""Generate all the HTML output"""
|
|
doc = h(h.html([
|
|
h.head(h.meta(charset="utf8"),
|
|
h.title("Implementation Report"),
|
|
h.link(href="report.css", rel="stylesheet")),
|
|
h.body(h.h1("Implementation Report"),
|
|
h.h2("Summary"),
|
|
summary(UAs, results_by_test),
|
|
h.h2("Full Results"),
|
|
h.table(
|
|
h.thead(
|
|
h.tr(
|
|
h.th("Test"),
|
|
h.th("Subtest"),
|
|
[h.th(UA) for UA in sorted(UAs)]
|
|
)
|
|
),
|
|
result_bodies(UAs, results_by_test)
|
|
)
|
|
)
|
|
]))
|
|
|
|
return doc
|
|
|
|
|
|
def main(filenames):
|
|
data = load_data(filenames)
|
|
UAs, results_by_test = group_results(data)
|
|
return generate_html(UAs, results_by_test)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if not sys.argv[1:]:
|
|
print """Please supply a list of UA name, filename pairs e.g.
|
|
|
|
python report.py Firefox firefox.json Chrome chrome.json IE internet_explorer.json"""
|
|
print main(sys.argv[1:])
|