# 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 json
import os
from collections import defaultdict
from datetime import datetime, timezone
from .. import base
html = None
raw = None
from html import escape
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= "/rev/".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. 0
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)
known_intermittent = data.get( "known_intermittent", [])
if status != expected and status not in known_intermittent:
status_name = "UNEXPECTED_" + status
elif status in known_intermittent:
status_name = "KNOWN_INTERMITTENT"
elif status not in ( "PASS", "SKIP"):
status_name = "EXPECTED_" + status
self.test_count[status_name] += 1
if status in [ "SKIP", "FAIL", "PRECONDITION_FAILED", "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:
if not isinstance(content, (str, bytes)):
# All types must be json serializable
content = json.dumps(content)
# Decode to text type if JSON output is byte string
if not isinstance(content, str):
content = content.decode( "utf-8")
# 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.
if isinstance(content, str):
is_known_utf8 = True
content_bytes = str(content).encode(
"utf-8", "xmlcharrefreplace"
)
else:
is_known_utf8 = False
content_bytes = content
meta = [ "text/html"]
if is_known_utf8:
meta.append( "charset=utf-8")
# base64 is ascii only, which means we don't have to care about encoding
# in the case where we don't know the encoding of the input
b64_bytes = base64.b64encode(content_bytes)
b64_text = b64_bytes.decode()
href = "data:%s;base64,%s" % ( ";".join(meta), b64_text)
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])
elif (
line.lower().find( "error") != - 1
or line.lower().find( "exception") != - 1
):
log.append(html.span(raw(escape(line)), class_= "error"))
else:
log.append(raw(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.now(timezone.utc)
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.values()),
(self.suite_times[ "end"] - self.suite_times[ "start"])
/ 1000. 0,
),
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.span(
"%i known intermittent results"
% self.test_count[ "KNOWN_INTERMITTENT"],
class_= "known_intermittent",
),
".",
),
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 "<!DOCTYPE html>\n" + doc.unicode(indent= 2)
¤ Dauer der Verarbeitung: 0.5 Sekunden
¤
*© Formatika GbR, Deutschland
|
|