tor-browser

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

commit d93c8a92267d146b9838e5f54261de44240cffe5
parent 93fe6627e2fa68ce7cfbabdc0b75a72a434972d8
Author: Sam Sneddon <gsnedders@apple.com>
Date:   Wed,  3 Dec 2025 14:41:33 +0000

Bug 1999064 [wpt PR 55950] - Make wptrunner.product.Product have a full constructor, a=testonly

Automatic update from web-platform-tests
Make wptrunner.product.Product have a full constructor

This opens us up to having Product objects that don't come from our
existing code path, and provides clearer documentation for what the
interface of a Product is meant to be.

--

wpt-commits: 638e3323132fc6f469ffe83535d36c200b52f61a
wpt-pr: 55950

Diffstat:
Mtesting/web-platform/tests/tools/wptrunner/wptrunner/browsers/wktr.py | 2+-
Atesting/web-platform/tests/tools/wptrunner/wptrunner/deprecated.py | 27+++++++++++++++++++++++++++
Mtesting/web-platform/tests/tools/wptrunner/wptrunner/products.py | 280++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtesting/web-platform/tests/tools/wptrunner/wptrunner/tests/browsers/test_webkitgtk.py | 2+-
Mtesting/web-platform/tests/tools/wptrunner/wptrunner/tests/test_products.py | 30++++++++++++++++++++++++------
Mtesting/web-platform/tests/tools/wptrunner/wptrunner/wptcommandline.py | 2+-
6 files changed, 304 insertions(+), 39 deletions(-)

diff --git a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/wktr.py b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/wktr.py @@ -18,7 +18,7 @@ from ..executors.executorwktr import ( # noqa: F401 ) -__wptrunner__ = {"product": "WebKitTestRunner", +__wptrunner__ = {"product": "wktr", "check_args": "check_args", "browser": "WKTRBrowser", "executor": { diff --git a/testing/web-platform/tests/tools/wptrunner/wptrunner/deprecated.py b/testing/web-platform/tests/tools/wptrunner/wptrunner/deprecated.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +import sys +from typing import TYPE_CHECKING, TypeVar + +if sys.version_info >= (3, 13): + from warnings import deprecated as deprecated +elif TYPE_CHECKING: + from typing_extensions import deprecated as deprecated +else: + _T = TypeVar("_T") + + class deprecated: + def __init__( + self, + message: str, + /, + *, + category: type[Warning] | None = DeprecationWarning, + stacklevel: int = 1, + ) -> None: + self.message = message + self.category = category + self.stacklevel = stacklevel + + def __call__(self, f: _T) -> _T: + return f diff --git a/testing/web-platform/tests/tools/wptrunner/wptrunner/products.py b/testing/web-platform/tests/tools/wptrunner/wptrunner/products.py @@ -1,12 +1,117 @@ -# mypy: allow-untyped-defs +from __future__ import annotations + import importlib +import warnings +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Any, + Protocol, + TypedDict, + overload, +) from .browsers import product_list +from .deprecated import deprecated + +if TYPE_CHECKING: + import sys + from types import ModuleType + + from mozlog.structuredlog import StructuredLogger + from wptserve.config import Config + + from .browsers import base as browsers_base + from .environment import TestEnvironment + from .executors.base import TestExecutor + from .testloader import Subsuite + + if sys.version_info >= (3, 9): + from collections.abc import Mapping, Sequence + else: + from typing import Mapping, Sequence + + if sys.version_info >= (3, 10): + from typing import TypeAlias + else: + from typing_extensions import TypeAlias + + +JSON: TypeAlias = "Mapping[str, 'JSON'] | Sequence['JSON'] | str | int | float | bool | None" + + +class CheckArgs(Protocol): + def __call__(self, **kwargs: Any) -> None: + ... + + +class EnvExtras(Protocol): + def __call__(self, **kwargs: Any) -> Sequence[object]: + ... + + +class BrowserKwargs(Protocol): + def __call__( + self, + logger: StructuredLogger, + test_type: str, + run_info_data: Mapping[str, JSON], + *, + config: Config, + subsuite: Subsuite, + **kwargs: Any, + ) -> Mapping[str, object]: + ... + + +class ExecutorKwargs(Protocol): + def __call__( + self, + logger: StructuredLogger, + test_type: str, + test_environment: TestEnvironment, + run_info_data: Mapping[str, JSON], + *, + subsuite: Subsuite, + **kwargs: Any, + ) -> Mapping[str, object]: + ... + + +class RunInfoExtras(Protocol): + def __call__( + self, logger: StructuredLogger, **kwargs: Any + ) -> Mapping[str, JSON]: + ... + + +class TimeoutMultiplier(Protocol): + def __call__( + self, test_type: str, run_info_data: Mapping[str, JSON], **kwargs: Any + ) -> float: + ... + +class _WptrunnerModuleDictOptional(TypedDict, total=False): + run_info_extras: str + update_properties: str -def product_module(config, product): + +class WptrunnerModuleDict(_WptrunnerModuleDictOptional): + product: str + browser: str | Mapping[str | None, str] + check_args: str + browser_kwargs: str + executor_kwargs: str + env_options: str + env_extras: str + timeout_multiplier: str + executor: Mapping[str, str] + + +def _product_module(product: str) -> ModuleType: if product not in product_list: - raise ValueError("Unknown product %s" % product) + raise ValueError(f"Unknown product {product!r}") module = importlib.import_module("wptrunner.browsers." + product) if not hasattr(module, "__wptrunner__"): @@ -15,35 +120,150 @@ def product_module(config, product): return module +def default_run_info_extras(logger: StructuredLogger, **kwargs: Any) -> Mapping[str, JSON]: + return {} + + +_legacy_product_msg = "Use Product.from_product_name(name) instead of Product(config, name)" + + +@dataclass class Product: - def __init__(self, config, product): - module = product_module(config, product) - data = module.__wptrunner__ - self.name = product - if isinstance(data["browser"], str): - self._browser_cls = {None: getattr(module, data["browser"])} + name: str + browser_classes: Mapping[str | None, type[browsers_base.Browser]] + check_args: CheckArgs + get_browser_kwargs: BrowserKwargs + get_executor_kwargs: ExecutorKwargs + env_options: Mapping[str, Any] + get_env_extras: EnvExtras + get_timeout_multiplier: TimeoutMultiplier + executor_classes: Mapping[str, type[TestExecutor]] + run_info_extras: RunInfoExtras + update_properties: tuple[Sequence[str], Mapping[str, Sequence[str]]] + + @overload + @deprecated(_legacy_product_msg, category=None) + def __init__( + self, + config: object, + legacy_name: str, + /, + *, + _do_not_use_allow_legacy_name_call: bool = False, + ) -> None: + ... + + @overload + def __init__( + self, + name: str, + *, + browser_classes: Mapping[str | None, type[browsers_base.Browser]], + check_args: CheckArgs, + get_browser_kwargs: BrowserKwargs, + get_executor_kwargs: ExecutorKwargs, + env_options: Mapping[str, Any], + get_env_extras: EnvExtras, + get_timeout_multiplier: TimeoutMultiplier, + executor_classes: Mapping[str, type[TestExecutor]], + run_info_extras: None | RunInfoExtras = None, + update_properties: None | tuple[Sequence[str], Mapping[str, Sequence[str]]] = None, + ) -> None: + ... + + def __init__( + self, + name: object, + _legacy_name: None | str = None, + *, + browser_classes: None | Mapping[str | None, type[browsers_base.Browser]] = None, + check_args: None | CheckArgs = None, + get_browser_kwargs: None | BrowserKwargs = None, + get_executor_kwargs: None | ExecutorKwargs = None, + env_options: None | Mapping[str, Any] = None, + get_env_extras: None | EnvExtras = None, + get_timeout_multiplier: None | TimeoutMultiplier = None, + executor_classes: None | Mapping[str, type[TestExecutor]] = None, + run_info_extras: None | RunInfoExtras = None, + update_properties: None | tuple[Sequence[str], Mapping[str, Sequence[str]]] = None, + _do_not_use_allow_legacy_name_call: bool = False, + ) -> None: + if _legacy_name is None: + assert isinstance(name, str) else: - self._browser_cls = {key: getattr(module, value) - for key, value in data["browser"].items()} - self.check_args = getattr(module, data["check_args"]) - self.get_browser_kwargs = getattr(module, data["browser_kwargs"]) - self.get_executor_kwargs = getattr(module, data["executor_kwargs"]) - self.env_options = getattr(module, data["env_options"])() - self.get_env_extras = getattr(module, data["env_extras"]) - self.run_info_extras = (getattr(module, data["run_info_extras"]) - if "run_info_extras" in data else lambda product, **kwargs:{}) - self.get_timeout_multiplier = getattr(module, data["timeout_multiplier"]) - - self.executor_classes = {} - for test_type, cls_name in data["executor"].items(): - cls = getattr(module, cls_name) - self.executor_classes[test_type] = cls - - self.update_properties = (getattr(module, data["update_properties"])() - if "update_properties" in data else (["product"], {})) - - - def get_browser_cls(self, test_type): + if not _do_not_use_allow_legacy_name_call: + warnings.warn(_legacy_product_msg, category=DeprecationWarning, stacklevel=2) + + module = _product_module(_legacy_name) + data: WptrunnerModuleDict = module.__wptrunner__ + + name = data["product"] + if name != _legacy_name: + msg = f"Product {_legacy_name!r} calls itself {name!r}, which differs" + raise ValueError(msg) + browser_classes = ( + {None: getattr(module, data["browser"])} + if isinstance(data["browser"], str) + else { + key: getattr(module, value) + for key, value in data["browser"].items() + } + ) + check_args = getattr(module, data["check_args"]) + get_browser_kwargs = getattr(module, data["browser_kwargs"]) + get_executor_kwargs = getattr(module, data["executor_kwargs"]) + env_options = getattr(module, data["env_options"])() + get_env_extras = getattr(module, data["env_extras"]) + get_timeout_multiplier = getattr(module, data["timeout_multiplier"]) + executor_classes = { + test_type: getattr(module, cls_name) + for test_type, cls_name in data["executor"].items() + } + run_info_extras = ( + getattr(module, data["run_info_extras"]) + if "run_info_extras" in data + else None + ) + update_properties = ( + getattr(module, data["update_properties"])() + if "update_properties" in data + else None + ) + + assert browser_classes is not None + assert check_args is not None + assert get_browser_kwargs is not None + assert get_executor_kwargs is not None + assert env_options is not None + assert get_env_extras is not None + assert get_timeout_multiplier is not None + assert executor_classes is not None + + self.name = name + self._browser_cls = browser_classes + self.check_args = check_args + self.get_browser_kwargs = get_browser_kwargs + self.get_executor_kwargs = get_executor_kwargs + self.env_options = env_options + self.get_env_extras = get_env_extras + self.get_timeout_multiplier = get_timeout_multiplier + self.executor_classes = executor_classes + + if run_info_extras is not None: + self.run_info_extras = run_info_extras + else: + self.run_info_extras = default_run_info_extras + + if update_properties is not None: + self.update_properties = update_properties + else: + self.update_properties = (["product"], {}) + + @classmethod + def from_product_name(cls, name: str) -> Product: + return cls(None, name, _do_not_use_allow_legacy_name_call=True) + + def get_browser_cls(self, test_type: str) -> type[browsers_base.Browser]: if test_type in self._browser_cls: return self._browser_cls[test_type] return self._browser_cls[None] diff --git a/testing/web-platform/tests/tools/wptrunner/wptrunner/tests/browsers/test_webkitgtk.py b/testing/web-platform/tests/tools/wptrunner/wptrunner/tests/browsers/test_webkitgtk.py @@ -30,7 +30,7 @@ def test_webkitgtk_certificate_domain_list(product): if product not in ["epiphany", "webkit", "webkitgtk_minibrowser"]: pytest.skip("%s doesn't support certificate_domain_list" % product) - product_data = products.Product({}, product) + product_data = products.Product.from_product_name(product) cert_file = "/home/user/wpt/tools/certs/cacert.pem" valid_domains_test = ["a.example.org", "b.example.org", "example.org", diff --git a/testing/web-platform/tests/tools/wptrunner/wptrunner/tests/test_products.py b/testing/web-platform/tests/tools/wptrunner/wptrunner/tests/test_products.py @@ -1,5 +1,6 @@ # mypy: allow-untyped-defs, allow-untyped-calls +import warnings from os.path import join, dirname from unittest import mock @@ -19,24 +20,41 @@ environment.do_delayed_imports(None, test_paths) @active_products("product") def test_load_active_product(product): """test we can successfully load the product of the current testenv""" - products.Product({}, product) + products.Product.from_product_name(product) # test passes if it doesn't throw @all_products("product") def test_load_all_products(product): """test every product either loads or throws ImportError""" - try: - products.Product({}, product) - except ImportError: - pass + with warnings.catch_warnings(): + # This acts to ensure that we don't get a DeprecationWarning here. + warnings.filterwarnings( + "error", + message=r"Use Product\.from_product_name", + category=DeprecationWarning, + ) + try: + products.Product.from_product_name(product) + except ImportError: + pass + + +@all_products("product") +def test_load_all_products_deprecated(product): + """test every product causes a DeprecationWarning""" + with pytest.deprecated_call(match=r"Use Product\.from_product_name"): + try: + products.Product({}, product) + except ImportError: + pass @active_products("product", marks={ "sauce": pytest.mark.skip("needs env extras kwargs"), }) def test_server_start_config(product): - product_data = products.Product({}, product) + product_data = products.Product.from_product_name(product) env_extras = product_data.get_env_extras() diff --git a/testing/web-platform/tests/tools/wptrunner/wptrunner/wptcommandline.py b/testing/web-platform/tests/tools/wptrunner/wptrunner/wptcommandline.py @@ -452,7 +452,7 @@ def set_from_config(kwargs): kwargs["config"] = config.read(kwargs["config_path"]) - kwargs["product"] = products.Product(kwargs["config"], kwargs["product"]) + kwargs["product"] = products.Product.from_product_name(kwargs["product"]) keys = {"paths": [("prefs", "prefs_root", "path"), ("run_info", "run_info", "path"),