diff options
Diffstat (limited to 'testing/mozbase/mozlog/mozlog/formatters/html/html.py')
-rwxr-xr-x | testing/mozbase/mozlog/mozlog/formatters/html/html.py | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/testing/mozbase/mozlog/mozlog/formatters/html/html.py b/testing/mozbase/mozlog/mozlog/formatters/html/html.py new file mode 100755 index 000000000..0ec244aa6 --- /dev/null +++ b/testing/mozbase/mozlog/mozlog/formatters/html/html.py @@ -0,0 +1,236 @@ +#!/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", {}) + # Add support for log exported from wptrunner. The structure of + # reftest_screenshots is listed in wptrunner/executors/base.py. + if debug.get('reftest_screenshots'): + log_data = debug.get("reftest_screenshots", {}) + debug = { + 'image1': 'data:image/png;base64,' + log_data[0].get("screenshot", {}), + 'image2': 'data:image/png;base64,' + log_data[2].get("screenshot", {}), + 'differences': "Not Implemented", + } + + 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('differences'): + images = [ + ('image1', 'Image 1 (test)'), + ('image2', 'Image 2 (reference)') + ] + for title, description in images: + screenshot = '%s' % debug[title] + additional_html.append(html.div( + html.a(html.img(src=screenshot), href="#"), + html.br(), + html.a(description), + class_='screenshot')) + + if debug.get('screenshot'): + screenshot = '%s' % debug['screenshot'] + screenshot = 'data:image/png;base64,' + screenshot + + additional_html.append(html.div( + html.a(html.img(src=screenshot), href="#"), + class_='screenshot')) + + for name, content in debug.items(): + if name in ['screenshot', 'image1', 'image2']: + if not content.startswith('data:image/png;base64,'): + href = 'data:image/png;base64,%s' % content + else: + href = content + else: + # Encode base64 to avoid that some browsers (such as Firefox, Opera) + # treats '#' as the start of another link if it is contained in the data URL. + # Use 'charset=utf-8' to show special characters like Chinese. + utf_encoded = unicode(content).encode('utf-8', 'xmlcharrefreplace') + href = 'data:text/html;charset=utf-8;base64,%s' % base64.b64encode(utf_encoded) + + 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) |