summaryrefslogtreecommitdiffstats
path: root/layout/tools/reftest/output.py
diff options
context:
space:
mode:
Diffstat (limited to 'layout/tools/reftest/output.py')
-rw-r--r--layout/tools/reftest/output.py190
1 files changed, 190 insertions, 0 deletions
diff --git a/layout/tools/reftest/output.py b/layout/tools/reftest/output.py
new file mode 100644
index 0000000000..fe435eb545
--- /dev/null
+++ b/layout/tools/reftest/output.py
@@ -0,0 +1,190 @@
+# 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 json
+import threading
+from collections import defaultdict
+
+from mozlog.formatters import TbplFormatter
+from mozrunner.utils import get_stack_fixer_function
+
+
+class ReftestFormatter(TbplFormatter):
+ """
+ Formatter designed to preserve the legacy "tbpl" format in reftest.
+
+ This is needed for both the reftest-analyzer and mozharness log parsing.
+ We can change this format when both reftest-analyzer and mozharness have
+ been changed to read structured logs.
+ """
+
+ def __call__(self, data):
+ if "component" in data and data["component"] == "mozleak":
+ # Output from mozleak requires that no prefix be added
+ # so that mozharness will pick up these failures.
+ return "%s\n" % data["message"]
+
+ formatted = TbplFormatter.__call__(self, data)
+
+ if formatted is None:
+ return
+ if data["action"] == "process_output":
+ return formatted
+ return "REFTEST %s" % formatted
+
+ def log(self, data):
+ prefix = "%s |" % data["level"].upper()
+ return "%s %s\n" % (prefix, data["message"])
+
+ def _format_status(self, data):
+ extra = data.get("extra", {})
+ status = data["status"]
+
+ status_msg = "TEST-"
+ if "expected" in data:
+ status_msg += "UNEXPECTED-%s" % status
+ else:
+ if status not in ("PASS", "SKIP"):
+ status_msg += "KNOWN-"
+ status_msg += status
+ if extra.get("status_msg") == "Random":
+ status_msg += "(EXPECTED RANDOM)"
+ return status_msg
+
+ def test_status(self, data):
+ extra = data.get("extra", {})
+ test = data["test"]
+
+ status_msg = self._format_status(data)
+ output_text = "%s | %s | %s" % (
+ status_msg,
+ test,
+ data.get("subtest", "unknown test"),
+ )
+ if data.get("message"):
+ output_text += " | %s" % data["message"]
+
+ if "reftest_screenshots" in extra:
+ screenshots = extra["reftest_screenshots"]
+ image_1 = screenshots[0]["screenshot"]
+
+ if len(screenshots) == 3:
+ image_2 = screenshots[2]["screenshot"]
+ output_text += (
+ "\nREFTEST IMAGE 1 (TEST): data:image/png;base64,%s\n"
+ "REFTEST IMAGE 2 (REFERENCE): data:image/png;base64,%s"
+ ) % (image_1, image_2)
+ elif len(screenshots) == 1:
+ output_text += "\nREFTEST IMAGE: data:image/png;base64,%s" % image_1
+
+ return output_text + "\n"
+
+ def test_end(self, data):
+ status = data["status"]
+ test = data["test"]
+
+ output_text = ""
+ if status != "OK":
+ status_msg = self._format_status(data)
+ output_text = "%s | %s | %s" % (status_msg, test, data.get("message", ""))
+
+ if output_text:
+ output_text += "\nREFTEST "
+ output_text += "TEST-END | %s" % test
+ return "%s\n" % output_text
+
+ def process_output(self, data):
+ return "%s\n" % data["data"]
+
+ def suite_end(self, data):
+ lines = []
+ summary = data["extra"]["results"]
+ summary["success"] = summary["Pass"] + summary["LoadOnly"]
+ lines.append(
+ "Successful: %(success)s (%(Pass)s pass, %(LoadOnly)s load only)" % summary
+ )
+ summary["unexpected"] = (
+ summary["Exception"]
+ + summary["FailedLoad"]
+ + summary["UnexpectedFail"]
+ + summary["UnexpectedPass"]
+ + summary["AssertionUnexpected"]
+ + summary["AssertionUnexpectedFixed"]
+ )
+ lines.append(
+ (
+ "Unexpected: %(unexpected)s (%(UnexpectedFail)s unexpected fail, "
+ "%(UnexpectedPass)s unexpected pass, "
+ "%(AssertionUnexpected)s unexpected asserts, "
+ "%(FailedLoad)s failed load, "
+ "%(Exception)s exception)"
+ )
+ % summary
+ )
+ summary["known"] = (
+ summary["KnownFail"]
+ + summary["AssertionKnown"]
+ + summary["Random"]
+ + summary["Skip"]
+ + summary["Slow"]
+ )
+ lines.append(
+ (
+ "Known problems: %(known)s ("
+ + "%(KnownFail)s known fail, "
+ + "%(AssertionKnown)s known asserts, "
+ + "%(Random)s random, "
+ + "%(Skip)s skipped, "
+ + "%(Slow)s slow)"
+ )
+ % summary
+ )
+ lines = ["REFTEST INFO | %s" % s for s in lines]
+ lines.append("REFTEST SUITE-END | Shutdown")
+ return "INFO | Result summary:\n{}\n".format("\n".join(lines))
+
+
+class OutputHandler(object):
+ """Process the output of a process during a test run and translate
+ raw data logged from reftest.js to an appropriate structured log action,
+ where applicable.
+ """
+
+ def __init__(self, log, utilityPath, symbolsPath=None):
+ self.stack_fixer_function = get_stack_fixer_function(utilityPath, symbolsPath)
+ self.log = log
+ self.proc_name = None
+ self.results = defaultdict(int)
+
+ def __call__(self, line):
+ # need to return processed messages to appease remoteautomation.py
+ if not line.strip():
+ return []
+ line = line.decode("utf-8", errors="replace")
+
+ try:
+ data = json.loads(line)
+ except ValueError:
+ self.verbatim(line)
+ return [line]
+
+ if isinstance(data, dict) and "action" in data:
+ if data["action"] == "results":
+ for k, v in data["results"].items():
+ self.results[k] += v
+ else:
+ self.log.log_raw(data)
+ else:
+ self.verbatim(json.dumps(data))
+
+ return [data]
+
+ def write(self, data):
+ return self.__call__(data)
+
+ def verbatim(self, line):
+ if self.stack_fixer_function:
+ line = self.stack_fixer_function(line)
+ name = self.proc_name or threading.current_thread().name
+ self.log.process_output(name, line)