blob: 431666551f748809f86bb1f8a917ffd255928f42 [file] [log] [blame]
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import base64
import cgi
from datetime import datetime
import os
from .. import base
from collections import defaultdict
html = None
raw = None
base_path = os.path.split(__file__)[0]
def do_defered_imports():
global html
global raw
from .xmlgen import html, raw
class HTMLFormatter(base.BaseFormatter):
"""Formatter that produces a simple HTML-formatted report."""
def __init__(self):
do_defered_imports()
self.suite_name = None
self.result_rows = []
self.test_count = defaultdict(int)
self.start_times = {}
self.suite_times = {"start": None,
"end": None}
self.head = None
self.env = {}
def suite_start(self, data):
self.suite_times["start"] = data["time"]
self.suite_name = data["source"]
with open(os.path.join(base_path, "style.css")) as f:
self.head = html.head(
html.meta(charset="utf-8"),
html.title(data["source"]),
html.style(raw(f.read())))
date_format = "%d %b %Y %H:%M:%S"
version_info = data.get("version_info")
if version_info:
self.env["Device identifier"] = version_info.get("device_id")
self.env["Device firmware (base)"] = version_info.get("device_firmware_version_base")
self.env["Device firmware (date)"] = (
datetime.utcfromtimestamp(int(version_info.get("device_firmware_date"))).strftime(date_format) if
"device_firmware_date" in version_info else None)
self.env["Device firmware (incremental)"] = version_info.get("device_firmware_version_incremental")
self.env["Device firmware (release)"] = version_info.get("device_firmware_version_release")
self.env["Gaia date"] = (
datetime.utcfromtimestamp(int(version_info.get("gaia_date"))).strftime(date_format) if
"gaia_date" in version_info else None)
self.env["Gecko version"] = version_info.get("application_version")
self.env["Gecko build"] = version_info.get("application_buildid")
if version_info.get("application_changeset"):
self.env["Gecko revision"] = version_info.get("application_changeset")
if version_info.get("application_repository"):
self.env["Gecko revision"] = html.a(
version_info.get("application_changeset"),
href="/".join([version_info.get("application_repository"),
version_info.get("application_changeset")]),
target="_blank")
if version_info.get("gaia_changeset"):
self.env["Gaia revision"] = html.a(
version_info.get("gaia_changeset")[:12],
href="https://github.com/mozilla-b2g/gaia/commit/%s" % version_info.get("gaia_changeset"),
target="_blank")
device_info = data.get("device_info")
if device_info:
self.env["Device uptime"] = device_info.get("uptime")
self.env["Device memory"] = device_info.get("memtotal")
self.env["Device serial"] = device_info.get("id")
def suite_end(self, data):
self.suite_times["end"] = data["time"]
return self.generate_html()
def test_start(self, data):
self.start_times[data["test"]] = data["time"]
def test_end(self, data):
self.make_result_html(data)
def make_result_html(self, data):
tc_time = (data["time"] - self.start_times.pop(data["test"])) / 1000.
additional_html = []
debug = data.get("extra", {})
links_html = []
status = status_name = data["status"]
expected = data.get("expected", status)
if status != expected:
status_name = "UNEXPECTED_" + status
elif status not in ("PASS", "SKIP"):
status_name = "EXPECTED_" + status
self.test_count[status_name] += 1
if status in ['SKIP', 'FAIL', 'ERROR']:
if debug.get('screenshot'):
screenshot = 'data:image/png;base64,%s' % debug['screenshot']
additional_html.append(html.div(
html.a(html.img(src=screenshot), href="#"),
class_='screenshot'))
for name, content in debug.items():
if 'screenshot' in name:
href = '#'
else:
# use base64 to avoid that some browser (such as Firefox, Opera)
# treats '#' as the start of another link if the data URL contains.
# use 'charset=utf-8' to show special characters like Chinese.
href = 'data:text/plain;charset=utf-8;base64,%s' % base64.b64encode(content.encode('utf-8'))
links_html.append(html.a(
name.title(),
class_=name,
href=href,
target='_blank'))
links_html.append(' ')
log = html.div(class_='log')
output = data.get('stack', '').splitlines()
output.extend(data.get('message', '').splitlines())
for line in output:
separator = line.startswith(' ' * 10)
if separator:
log.append(line[:80])
else:
if line.lower().find("error") != -1 or line.lower().find("exception") != -1:
log.append(html.span(raw(cgi.escape(line)), class_='error'))
else:
log.append(raw(cgi.escape(line)))
log.append(html.br())
additional_html.append(log)
self.result_rows.append(
html.tr([html.td(status_name, class_='col-result'),
html.td(data['test'], class_='col-name'),
html.td('%.2f' % tc_time, class_='col-duration'),
html.td(links_html, class_='col-links'),
html.td(additional_html, class_='debug')],
class_=status_name.lower() + ' results-table-row'))
def generate_html(self):
generated = datetime.utcnow()
with open(os.path.join(base_path, "main.js")) as main_f:
doc = html.html(
self.head,
html.body(
html.script(raw(main_f.read())),
html.p('Report generated on %s at %s' % (
generated.strftime('%d-%b-%Y'),
generated.strftime('%H:%M:%S'))),
html.h2('Environment'),
html.table(
[html.tr(html.td(k), html.td(v)) for k, v in sorted(self.env.items()) if v],
id='environment'),
html.h2('Summary'),
html.p('%i tests ran in %.1f seconds.' % (sum(self.test_count.itervalues()),
(self.suite_times["end"] -
self.suite_times["start"]) / 1000.),
html.br(),
html.span('%i passed' % self.test_count["PASS"], class_='pass'), ', ',
html.span('%i skipped' % self.test_count["SKIP"], class_='skip'), ', ',
html.span('%i failed' % self.test_count["UNEXPECTED_FAIL"], class_='fail'), ', ',
html.span('%i errors' % self.test_count["UNEXPECTED_ERROR"], class_='error'), '.',
html.br(),
html.span('%i expected failures' % self.test_count["EXPECTED_FAIL"],
class_='expected_fail'), ', ',
html.span('%i unexpected passes' % self.test_count["UNEXPECTED_PASS"],
class_='unexpected_pass'), '.'),
html.h2('Results'),
html.table([html.thead(
html.tr([
html.th('Result', class_='sortable', col='result'),
html.th('Test', class_='sortable', col='name'),
html.th('Duration', class_='sortable numeric', col='duration'),
html.th('Links')]), id='results-table-head'),
html.tbody(self.result_rows, id='results-table-body')], id='results-table')))
return u"<!DOCTYPE html>\n" + doc.unicode(indent=2)