Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/python/mozperftest/mozperftest/test/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 16 kB image not shown  

Quelle  mochitest.py

  Sprache: 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 json
import os
import sys
from contextlib import redirect_stdout
from pathlib import Path

from mozperftest.layers import Layer
from mozperftest.test.functionaltestrunner import (
    FunctionalTestRunner,
)
from mozperftest.utils import (
    EVAL_DATA_MATCHER,
    ON_TRY,
    PERF_METRICS_MATCHER,
    LogProcessor,
    NoEvalDataError,
    NoPerfMetricsError,
    install_requirements_file,
)


class MissingMochitestInformation(Exception):
    """Raised when information needed to run a mochitest is missing."""

    pass


class MochitestTestFailure(Exception):
    """Raised when a mochitest test returns a non-zero exit code."""

    pass


class MochitestData:
    def open_data(self, data):
        return {
            "name""mochitest",
            "subtest": data["name"],
            "data": [
                {"file""mochitest""value": value, "xaxis": xaxis}
                for xaxis, value in enumerate(data["values"])
            ],
            "value": data.get("value"None),
            "unit": data.get("unit"None),
            "shouldAlert": data.get("shouldAlert"None),
            "lowerIsBetter": data.get("lowerIsBetter"None),
        }

    def transform(self, data):
        return data

    merge = transform


class _Mochitest(Layer):
    """Runs a mochitest test through `mach test` locally, and directly with mochitest in CI."""

    name = "mochitest"
    activated = True

    arguments = {
        "binary": {
            "type": str,
            "default"None,
            "help": ("Path to the browser."),
        },
        "cycles": {
            "type": int,
            "default"1,
            "help": ("Number of cycles/iterations to do for the test."),
        },
        "manifest": {
            "type": str,
            "default"None,
            "help": (
                "Path to the manifest that contains the test (only required in CI)."
            ),
        },
        "manifest-flavor": {
            "type": str,
            "default"None,
            "help""Mochitest flavor of the test to run (only required in CI).",
        },
        "extra-args": {
            "nargs""*",
            "type": str,
            "default": [],
            "help": (
                "Additional arguments to pass to mochitest. Expected in a format such as: "
                "--mochitest-extra-args headless profile-path=/path/to/profile"
            ),
        },
        "name-change": {
            "action""store_true",
            "default"False,
            "help""Use the test name from the metadata instead of the test filename.",
        },
    }

    def __init__(self, env, mach_cmd):
        super().__init__(env, mach_cmd)
        self.topsrcdir = mach_cmd.topsrcdir
        self._mach_context = mach_cmd._mach_context
        self.python_path = mach_cmd.virtualenv_manager.python_path
        self.topobjdir = mach_cmd.topobjdir
        self.distdir = mach_cmd.distdir
        self.bindir = mach_cmd.bindir
        self.statedir = mach_cmd.statedir
        self.payloads_from_log = []
        self.topsrcdir = mach_cmd.topsrcdir

    def setup(self):
        if ON_TRY:
            # Install marionette requirements
            install_requirements_file(
                self.mach_cmd.virtualenv_manager,
                str(
                    Path(
                        os.getenv("MOZ_FETCHES_DIR"),
                        "config",
                        "marionette_requirements.txt",
                    )
                ),
            )

    def _enable_gecko_profiling(self):
        """Setup gecko profiling if requested."""
        gecko_profile_args = []

        gecko_profile_features = os.getenv(
            "MOZ_PROFILER_STARTUP_FEATURES""js,stackwalk,cpu,screenshots,memory"
        )
        gecko_profile_threads = os.getenv(
            "MOZ_PROFILER_STARTUP_FILTERS""GeckoMain,Compositor,Renderer"
        )
        gecko_profile_entries = os.getenv("MOZ_PROFILER_STARTUP_ENTRIES""65536000")
        gecko_profile_interval = os.getenv("MOZ_PROFILER_STARTUP_INTERVAL"None)

        if self.get_arg("gecko-profile"):
            gecko_profile_args.append("--profiler")
            gecko_profile_args.extend([
                f"--setenv=MOZ_PROFILER_STARTUP_FEATURES={gecko_profile_features}",
                f"--setenv=MOZ_PROFILER_STARTUP_FILTERS={gecko_profile_threads}",
                f"--setenv=MOZ_PROFILER_STARTUP_ENTRIES={gecko_profile_entries}",
            ])
            if gecko_profile_interval:
                gecko_profile_args.append(
                    f"--setenv=MOZ_PROFILER_STARTUP_INTERVAL={gecko_profile_interval}"
                )
            if ON_TRY:
                gecko_profile_args.append("--profiler-save-only")

                output_dir_path = str(Path(self.get_arg("output")).resolve())
                gecko_profile_args.append(f"--setenv=MOZ_UPLOAD_DIR={output_dir_path}")
            else:
                # Setup where the profile gets saved to so it doesn't get deleted
                profile_path = os.getenv("MOZ_PROFILER_SHUTDOWN")
                if not profile_path:
                    output_dir = Path(self.get_arg("output"))
                    if not output_dir.is_absolute():
                        output_dir = Path(self.topsrcdir, output_dir)
                    output_dir.resolve().mkdir(parents=True, exist_ok=True)
                    profile_path = output_dir / "profile_mochitest.json"
                    os.environ["MOZ_PROFILER_SHUTDOWN"] = str(profile_path)
                self.info(f"Profile will be saved to: {profile_path}")

        return gecko_profile_args

    def _parse_extra_args(self):
        """Sets up the extra-args from the user for passing to mochitest."""
        parsed_extra_args = []
        for arg in self.get_arg("extra-args"):
            parsed_extra_args.append(f"--{arg}")
        return parsed_extra_args

    def _parse_browser_prefs(self, metadata):
        """Sets up browser prefs from metadata for passing to mochitest."""
        mochitest_prefs = []
        browser_prefs = metadata.get_options("browser_prefs")
        for key, value in browser_prefs.items():
            mochitest_prefs.append(f"--setpref={key}={value}")
        return mochitest_prefs

    def _setup_mochitest_android_args(self, metadata):
        """Sets up all the arguments needed to run mochitest android tests."""
        app = metadata.binary
        activity = self.get_arg("android-activity")
        if (app + "."in activity:
            # Mochitest prefixes the activity with the app-name so we need to
            # remove it here if it exists.
            activity = activity.replace(app + ".""")

        mochitest_android_args = [
            "--android",
            f"--app={app}",
            f"--activity={activity}",
        ]

        if not ON_TRY:
            os.environ["MOZ_HOST_BIN"] = self.mach_cmd.bindir
            mochitest_android_args.extend([
                f"--setenv=MOZ_HOST_BIN={os.environ['MOZ_HOST_BIN']}",
            ])
        else:
            os.environ["MOZ_HOST_BIN"] = str(
                Path(os.getenv("MOZ_FETCHES_DIR"), "hostutils")
            )
            mochitest_android_args.extend([
                f"--setenv=MOZ_HOST_BIN={os.environ['MOZ_HOST_BIN']}",
                f"--remote-webserver={os.environ['HOST_IP']}",
                "--http-port=8854",
                "--ssl-port=4454",
            ])

        return mochitest_android_args

    def _get_mochitest_args(self, metadata):
        """Handles setup for all mochitest-specific arguments."""
        mochitest_args = []

        mochitest_args.extend(self._enable_gecko_profiling())
        mochitest_args.extend(self._parse_extra_args())
        mochitest_args.extend(self._parse_browser_prefs(metadata))

        if self.get_arg("android"):
            mochitest_args.extend(self._setup_mochitest_android_args(metadata))

        return mochitest_args

    def remote_run(self, test, metadata):
        """Run tests in CI."""
        import runtests
        import runtestsremote
        from manifestparser import TestManifest
        from mochitest_options import MochitestArgumentParser

        manifest_flavor = self.get_arg("manifest-flavor")
        manifest_name = self.get_arg("manifest")
        if not manifest_name:
            raise MissingMochitestInformation(
                "Name of manifest that contains test needs to be"
                "specified (e.g. mochitest-common.ini)"
            )
        if not manifest_flavor:
            raise MissingMochitestInformation(
                "Mochitest flavor needs to be provided(e.g. plain, browser-chrome, ...)"
            )

        manifest_path = Path(test.parent, manifest_name)
        manifest = TestManifest([str(manifest_path)], strict=False)
        test_list = manifest.active_tests(paths=(str(test),))

        subsuite = None
        for parsed_test in test_list or []:
            if str(test) in str(Path(parsed_test.get("path"""))):
                subsuite = parsed_test.get("subsuite"None)
                break

        # Use the mochitest argument parser to parse the extra argument
        # options, and produce an `args` object that has all the defaults
        parser = MochitestArgumentParser()
        args = parser.parse_args(self._get_mochitest_args(metadata))

        # Bug 1858155 - Attempting to only use one test_path triggers a failure
        # during test execution
        args.test_paths = [str(test.name), str(test.name)]
        args.keep_open = False
        args.runByManifest = True
        args.manifestFile = manifest
        args.topobjdir = self.topobjdir
        args.topsrcdir = self.topsrcdir
        args.flavor = manifest_flavor

        if subsuite:
            args.subsuite = subsuite

        fetch_dir = os.getenv("MOZ_FETCHES_DIR")
        if self.get_arg("android"):
            args.utilityPath = str(Path(fetch_dir, "hostutils"))
            args.xrePath = str(Path(fetch_dir, "hostutils"))
            args.extraProfileFiles.append(str(Path(fetch_dir, "bin""plugins")))
            args.testingModulesDir = str(Path(fetch_dir, "modules"))
            args.symbolsPath = str(Path(fetch_dir, "crashreporter-symbols"))
            args.certPath = str(Path(fetch_dir, "certs"))
        else:
            args.app = self.get_arg("mochitest_binary")
            args.utilityPath = str(Path(fetch_dir, "bin"))
            args.extraProfileFiles.append(str(Path(fetch_dir, "bin""plugins")))
            args.testingModulesDir = str(Path(fetch_dir, "modules"))
            args.symbolsPath = str(Path(fetch_dir, "crashreporter-symbols"))
            args.certPath = str(Path(fetch_dir, "certs"))

        log_processor = self._get_log_processor()

        with redirect_stdout(log_processor):
            # Perftest calls mochitest in-process, so there's no mozharness
            # layer to convert structured logs to TBPL. Request TBPL format
            # explicitly so the log processor sees human-readable lines.
            args.log_tbpl = [sys.stdout]
            if self.get_arg("android"):
                result = runtestsremote.run_test_harness(parser, args)
            else:
                result = runtests.run_test_harness(parser, args)

        return result, log_processor

    def run(self, metadata):
        test = Path(metadata.script["filename"])
        if self.get_arg("name-change"False):
            test_name = metadata.script["name"]
        else:
            test_name = test.name

        cycles = self.get_arg("cycles"1)
        for cycle in range(1, cycles + 1):
            metadata.run_hook(
                "before_cycle", metadata, self.env, cycle, metadata.script
            )
            try:
                if ON_TRY:
                    status, log_processor = self.remote_run(test, metadata)
                else:
                    status, log_processor = FunctionalTestRunner.test(
                        self.mach_cmd,
                        [str(test)],
                        self._get_mochitest_args(metadata) + ["--keep-open=False"],
                    )
            finally:
                metadata.run_hook(
                    "after_cycle", metadata, self.env, cycle, metadata.script
                )

            if status is not None and status != 0:
                raise MochitestTestFailure("Test failed to run")

            self._extract_payload_from_log(log_processor, metadata)

        self._handle_payloads(metadata, test_name)

        return metadata

    @staticmethod
    def _get_log_processor():
        raise NotImplementedError

    def _extract_payload_from_log(self, log_processor, metadata):
        """The payload for perftests and evals are output to the log, and extracted
        into the mozperftest harness for processing."""
        raise NotImplementedError

    def _handle_payloads(self, metadata, test_name):
        """After the payloads are extracting from the log, handle the final processing."""
        raise NotImplementedError


class PerfMochitest(_Mochitest):
    """A mochitest that collects the `perfResults` from stdout"""

    @staticmethod
    def _get_log_processor():
        return LogProcessor(PERF_METRICS_MATCHER)

    def _extract_payload_from_log(self, log_processor, metadata):
        """Parse metrics found"""
        for metrics_line in log_processor.match:
            self.payloads_from_log.append(
                json.loads(metrics_line.split("|")[-1].strip())
            )

    def _handle_payloads(self, metadata, test_name):
        results = []
        for payload in self.payloads_from_log:
            # Expecting results like {"metric-name": value, "metric-name2": value, ...}
            if isinstance(payload, dict):
                for key, val in payload.items():
                    for r in results:
                        if r["name"] == key:
                            r["values"].append(val)
                            break
                    else:
                        results.append({"name": key, "values": [val]})
            # Expecting results like [
            #     {"name": "metric-name", "values": [value1, value2, ...], ...},
            #     {"name": "metric-name2", "values": [value1, value2, ...], ...},
            # ]
            else:
                for metric in payload:
                    for r in results:
                        if r["name"] == metric["name"]:
                            r["values"].extend(metric["values"])
                            break
                    else:
                        results.append(metric)

        if not results:
            raise NoPerfMetricsError("mochitest")

        metadata.add_result({
            "name": test_name,
            "framework": {"name""mozperftest"},
            "transformer""mozperftest.test.mochitest:MochitestData",
            "results": results,
        })


class EvalMochitest(_Mochitest):
    """A mochitest that collects the `evalDataPayload` from stdout"""

    @staticmethod
    def _get_log_processor():
        return LogProcessor(EVAL_DATA_MATCHER)

    def _extract_payload_from_log(self, log_processor, metadata):
        """Parse the eval data payload from the log."""
        for eval_line in log_processor.match:
            self.payloads_from_log.append(
                # Take:
                #   "evalDataPayload | { ... }"
                # Partition into:
                #   ('evalDataPayload ', '|', " { ... }")
                # Then extract the payload:
                #   "{ ... }"
                # And finally load it as JSON
                #   { ... }
                json.loads(eval_line.partition("|")[2].strip())
            )

    def _handle_payloads(self, metadata, test_name):
        if not self.payloads_from_log:
            raise NoEvalDataError("mochitest")

        output_dir = Path(self.get_arg("output")).resolve()
        output_dir.mkdir(parents=True, exist_ok=True)
        out_file = output_dir / f"{Path(test_name).stem}-eval-data.json"
        pretty_json = json.dumps(self.payloads_from_log, indent=2)
        out_file.write_text(pretty_json)

        try:
            display_path = out_file.relative_to(Path(self.topsrcdir))
        except ValueError:
            display_path = out_file

        print(f"Evaluation data written to {display_path}")

        metadata.add_eval_payload(test_name, self.payloads_from_log)

Messung V0.5 in Prozent
C=95 H=92 G=93

¤ Dauer der Verarbeitung: 0.7 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.