tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

output.py (6384B)


      1 # This Source Code Form is subject to the terms of the Mozilla Public
      2 # License, v. 2.0. If a copy of the MPL was not distributed with this
      3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      4 
      5 import json
      6 import threading
      7 from collections import defaultdict
      8 
      9 from mozlog.formatters import TbplFormatter
     10 from mozrunner.utils import get_stack_fixer_function
     11 
     12 
     13 class ReftestFormatter(TbplFormatter):
     14    """
     15    Formatter designed to preserve the legacy "tbpl" format in reftest.
     16 
     17    This is needed for both the reftest-analyzer and mozharness log parsing.
     18    We can change this format when both reftest-analyzer and mozharness have
     19    been changed to read structured logs.
     20    """
     21 
     22    def __call__(self, data):
     23        if "component" in data and data["component"] == "mozleak":
     24            # Output from mozleak requires that no prefix be added
     25            # so that mozharness will pick up these failures.
     26            return "%s\n" % data["message"]
     27 
     28        formatted = TbplFormatter.__call__(self, data)
     29 
     30        if formatted is None:
     31            return
     32        if data["action"] == "process_output":
     33            return formatted
     34        return "REFTEST %s" % formatted
     35 
     36    def log(self, data):
     37        prefix = "%s |" % data["level"].upper()
     38        return "%s %s\n" % (prefix, data["message"])
     39 
     40    def _format_status(self, data):
     41        extra = data.get("extra", {})
     42        status = data["status"]
     43 
     44        status_msg = "TEST-"
     45        if "expected" in data:
     46            status_msg += "UNEXPECTED-%s" % status
     47        else:
     48            if status not in ("PASS", "SKIP"):
     49                status_msg += "KNOWN-"
     50            status_msg += status
     51            if extra.get("status_msg") == "Random":
     52                status_msg += "(EXPECTED RANDOM)"
     53        return status_msg
     54 
     55    def test_status(self, data):
     56        extra = data.get("extra", {})
     57        test = data["test"]
     58 
     59        status_msg = self._format_status(data)
     60        output_text = "%s | %s | %s" % (
     61            status_msg,
     62            test,
     63            data.get("subtest", "unknown test"),
     64        )
     65        if data.get("message"):
     66            output_text += " | %s" % data["message"]
     67 
     68        if "reftest_screenshots" in extra:
     69            screenshots = extra["reftest_screenshots"]
     70            image_1 = screenshots[0]["screenshot"]
     71 
     72            if len(screenshots) == 3:
     73                image_2 = screenshots[2]["screenshot"]
     74                output_text += (
     75                    "\nREFTEST   IMAGE 1 (TEST): data:image/png;base64,%s\n"
     76                    "REFTEST   IMAGE 2 (REFERENCE): data:image/png;base64,%s"
     77                ) % (image_1, image_2)
     78            elif len(screenshots) == 1:
     79                output_text += "\nREFTEST   IMAGE: data:image/png;base64,%s" % image_1
     80 
     81        return output_text + "\n"
     82 
     83    def test_end(self, data):
     84        status = data["status"]
     85        test = data["test"]
     86 
     87        output_text = ""
     88        if status != "OK":
     89            status_msg = self._format_status(data)
     90            output_text = "%s | %s | %s" % (status_msg, test, data.get("message", ""))
     91 
     92        if output_text:
     93            output_text += "\nREFTEST "
     94        output_text += "TEST-END | %s" % test
     95        return "%s\n" % output_text
     96 
     97    def process_output(self, data):
     98        return "%s\n" % data["data"]
     99 
    100    def suite_end(self, data):
    101        lines = []
    102        summary = data["extra"]["results"]
    103        summary["success"] = summary["Pass"] + summary["LoadOnly"]
    104        lines.append(
    105            "Successful: %(success)s (%(Pass)s pass, %(LoadOnly)s load only)" % summary
    106        )
    107        summary["unexpected"] = (
    108            summary["Exception"]
    109            + summary["FailedLoad"]
    110            + summary["UnexpectedFail"]
    111            + summary["UnexpectedPass"]
    112            + summary["AssertionUnexpected"]
    113            + summary["AssertionUnexpectedFixed"]
    114        )
    115        lines.append(
    116            (
    117                "Unexpected: %(unexpected)s (%(UnexpectedFail)s unexpected fail, "
    118                "%(UnexpectedPass)s unexpected pass, "
    119                "%(AssertionUnexpected)s unexpected asserts, "
    120                "%(FailedLoad)s failed load, "
    121                "%(Exception)s exception)"
    122            )
    123            % summary
    124        )
    125        summary["known"] = (
    126            summary["KnownFail"]
    127            + summary["AssertionKnown"]
    128            + summary["Random"]
    129            + summary["Skip"]
    130            + summary["Slow"]
    131        )
    132        lines.append(
    133            (
    134                "Known problems: %(known)s ("
    135                + "%(KnownFail)s known fail, "
    136                + "%(AssertionKnown)s known asserts, "
    137                + "%(Random)s random, "
    138                + "%(Skip)s skipped, "
    139                + "%(Slow)s slow)"
    140            )
    141            % summary
    142        )
    143        lines = ["REFTEST INFO | %s" % s for s in lines]
    144        lines.append("REFTEST SUITE-END | Shutdown")
    145        return "INFO | Result summary:\n{}\n".format("\n".join(lines))
    146 
    147 
    148 class OutputHandler:
    149    """Process the output of a process during a test run and translate
    150    raw data logged from reftest.js to an appropriate structured log action,
    151    where applicable.
    152    """
    153 
    154    def __init__(self, log, utilityPath, symbolsPath=None):
    155        self.stack_fixer_function = get_stack_fixer_function(utilityPath, symbolsPath)
    156        self.log = log
    157        self.proc_name = None
    158        self.results = defaultdict(int)
    159 
    160    def __call__(self, line):
    161        # need to return processed messages to appease remoteautomation.py
    162        if not line.strip():
    163            return []
    164        line = line.decode("utf-8", errors="replace")
    165 
    166        try:
    167            data = json.loads(line)
    168        except ValueError:
    169            self.verbatim(line)
    170            return [line]
    171 
    172        if isinstance(data, dict) and "action" in data:
    173            if data["action"] == "results":
    174                for k, v in data["results"].items():
    175                    self.results[k] += v
    176            else:
    177                self.log.log_raw(data)
    178        else:
    179            self.verbatim(json.dumps(data))
    180 
    181        return [data]
    182 
    183    def write(self, data):
    184        return self.__call__(data)
    185 
    186    def verbatim(self, line):
    187        if self.stack_fixer_function:
    188            line = self.stack_fixer_function(line)
    189        name = self.proc_name or threading.current_thread().name
    190        self.log.process_output(name, line)