tor-browser

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

item.py (11630B)


      1 import os.path
      2 from abc import ABCMeta, abstractproperty
      3 from inspect import isabstract
      4 from typing import (Any, Dict, Hashable, List, Optional, Sequence, Text, Tuple, Type,
      5                    TYPE_CHECKING, Union, cast)
      6 from urllib.parse import urljoin, urlparse, parse_qs
      7 
      8 from .utils import to_os_path
      9 
     10 if TYPE_CHECKING:
     11    from .manifest import Manifest
     12 
     13 Fuzzy = Dict[Optional[Tuple[str, str, str]], List[int]]
     14 PageRanges = Dict[str, List[int]]
     15 item_types: Dict[str, Type["ManifestItem"]] = {}
     16 
     17 
     18 class ManifestItemMeta(ABCMeta):
     19    """Custom metaclass that registers all the subclasses in the
     20    item_types dictionary according to the value of their item_type
     21    attribute, and otherwise behaves like an ABCMeta."""
     22 
     23    def __new__(cls: Type["ManifestItemMeta"], name: str, bases: Tuple[type], attrs: Dict[str, Any]) -> "ManifestItemMeta":
     24        inst = super().__new__(cls, name, bases, attrs)
     25        if isabstract(inst):
     26            return inst
     27 
     28        assert issubclass(inst, ManifestItem)
     29        item_type = cast(str, inst.item_type)
     30 
     31        item_types[item_type] = inst
     32 
     33        return inst
     34 
     35 
     36 class ManifestItem(metaclass=ManifestItemMeta):
     37    __slots__ = ("_tests_root", "path")
     38 
     39    def __init__(self, tests_root: Text, path: Text) -> None:
     40        self._tests_root = tests_root
     41        self.path = path
     42 
     43    @abstractproperty
     44    def id(self) -> Text:
     45        """The test's id (usually its url)"""
     46        pass
     47 
     48    @abstractproperty
     49    def item_type(self) -> str:
     50        """The item's type"""
     51        pass
     52 
     53    @property
     54    def path_parts(self) -> Tuple[Text, ...]:
     55        return tuple(self.path.split(os.path.sep))
     56 
     57    def key(self) -> Hashable:
     58        """A unique identifier for the test"""
     59        return (self.item_type, self.id)
     60 
     61    def __eq__(self, other: Any) -> bool:
     62        if not hasattr(other, "key"):
     63            return False
     64        return bool(self.key() == other.key())
     65 
     66    def __hash__(self) -> int:
     67        return hash(self.key())
     68 
     69    def __repr__(self) -> str:
     70        return f"<{self.__module__}.{self.__class__.__name__} id={self.id!r}, path={self.path!r}>"
     71 
     72    def to_json(self) -> Tuple[Any, ...]:
     73        return ()
     74 
     75    @classmethod
     76    def from_json(cls,
     77                  manifest: "Manifest",
     78                  path: Text,
     79                  obj: Any
     80                  ) -> "ManifestItem":
     81        path = to_os_path(path)
     82        tests_root = manifest.tests_root
     83        assert tests_root is not None
     84        return cls(tests_root, path)
     85 
     86 
     87 class URLManifestItem(ManifestItem):
     88    __slots__ = ("url_base", "_url", "_extras", "_flags")
     89 
     90    def __init__(self,
     91                 tests_root: Text,
     92                 path: Text,
     93                 url_base: Text,
     94                 url: Optional[Text],
     95                 **extras: Any
     96                 ) -> None:
     97        super().__init__(tests_root, path)
     98        assert url_base[0] == "/"
     99        self.url_base = url_base
    100        assert url is None or url[0] != "/"
    101        self._url = url
    102        self._extras = extras
    103        parsed_url = urlparse(self.url)
    104        self._flags = (set(parsed_url.path.rsplit("/", 1)[1].split(".")[1:-1]) |
    105                       set(parse_qs(parsed_url.query).get("wpt_flags", [])))
    106 
    107    @property
    108    def id(self) -> Text:
    109        return self.url
    110 
    111    @property
    112    def url(self) -> Text:
    113        rel_url = self._url or self.path.replace(os.path.sep, "/")
    114        # we can outperform urljoin, because we know we just have path relative URLs
    115        if self.url_base == "/":
    116            return "/" + rel_url
    117        return urljoin(self.url_base, rel_url)
    118 
    119    @property
    120    def https(self) -> bool:
    121        return "https" in self._flags or "serviceworker" in self._flags or "serviceworker-module" in self._flags
    122 
    123    @property
    124    def h2(self) -> bool:
    125        return "h2" in self._flags
    126 
    127    @property
    128    def subdomain(self) -> bool:
    129        # Note: this is currently hard-coded to check for `www`, rather than
    130        # all possible valid subdomains. It can be extended if needed.
    131        return "www" in self._flags
    132 
    133    def to_json(self) -> Tuple[Optional[Text], Dict[Any, Any]]:
    134        rel_url = None if self._url == self.path.replace(os.path.sep, "/") else self._url
    135        rv: Tuple[Optional[Text], Dict[Any, Any]] = (rel_url, {})
    136        return rv
    137 
    138    @classmethod
    139    def from_json(cls,
    140                  manifest: "Manifest",
    141                  path: Text,
    142                  obj: Tuple[Text, Dict[Any, Any]]
    143                  ) -> "URLManifestItem":
    144        path = to_os_path(path)
    145        url, extras = obj
    146        tests_root = manifest.tests_root
    147        assert tests_root is not None
    148        return cls(tests_root,
    149                   path,
    150                   manifest.url_base,
    151                   url,
    152                   **extras)
    153 
    154 
    155 class TestharnessTest(URLManifestItem):
    156    __slots__ = ()
    157 
    158    item_type = "testharness"
    159 
    160    @property
    161    def timeout(self) -> Optional[Text]:
    162        return self._extras.get("timeout")
    163 
    164    @property
    165    def pac(self) -> Optional[Text]:
    166        return self._extras.get("pac")
    167 
    168    @property
    169    def testdriver_features(self) -> Optional[List[Text]]:
    170        return self._extras.get("testdriver_features")
    171 
    172    @property
    173    def testdriver(self) -> Optional[bool]:
    174        return self._extras.get("testdriver")
    175 
    176    @property
    177    def jsshell(self) -> Optional[Text]:
    178        return self._extras.get("jsshell")
    179 
    180    @property
    181    def script_metadata(self) -> Optional[List[Tuple[Text, Text]]]:
    182        return self._extras.get("script_metadata")
    183 
    184    def to_json(self) -> Tuple[Optional[Text], Dict[Text, Any]]:
    185        rv = super().to_json()
    186        if self.timeout is not None:
    187            rv[-1]["timeout"] = self.timeout
    188        if self.pac is not None:
    189            rv[-1]["pac"] = self.pac
    190        if self.testdriver_features is not None:
    191            rv[-1]["testdriver_features"] = self.testdriver_features
    192        if self.testdriver:
    193            rv[-1]["testdriver"] = self.testdriver
    194        if self.jsshell:
    195            rv[-1]["jsshell"] = True
    196        if self.script_metadata:
    197            rv[-1]["script_metadata"] = [(k, v) for (k,v) in self.script_metadata]
    198        return rv
    199 
    200 
    201 class RefTest(URLManifestItem):
    202    __slots__ = ("references",)
    203 
    204    item_type = "reftest"
    205 
    206    def __init__(self,
    207                 tests_root: Text,
    208                 path: Text,
    209                 url_base: Text,
    210                 url: Optional[Text],
    211                 references: Optional[List[Tuple[Text, Text]]] = None,
    212                 **extras: Any
    213                 ):
    214        super().__init__(tests_root, path, url_base, url, **extras)
    215        if references is None:
    216            self.references: List[Tuple[Text, Text]] = []
    217        else:
    218            self.references = references
    219 
    220    @property
    221    def timeout(self) -> Optional[Text]:
    222        return self._extras.get("timeout")
    223 
    224    @property
    225    def viewport_size(self) -> Optional[Text]:
    226        return self._extras.get("viewport_size")
    227 
    228    @property
    229    def dpi(self) -> Optional[Text]:
    230        return self._extras.get("dpi")
    231 
    232    @property
    233    def fuzzy(self) -> Fuzzy:
    234        fuzzy: Union[Fuzzy, List[Tuple[Optional[Sequence[Text]], List[int]]]] = self._extras.get("fuzzy", {})
    235        if not isinstance(fuzzy, list):
    236            return fuzzy
    237 
    238        rv: Fuzzy = {}
    239        for k, v in fuzzy:  # type: Tuple[Optional[Sequence[Text]], List[int]]
    240            if k is None:
    241                key: Optional[Tuple[Text, Text, Text]] = None
    242            else:
    243                # mypy types this as Tuple[Text, ...]
    244                assert len(k) == 3
    245                key = tuple(k)  # type: ignore
    246            rv[key] = v
    247        return rv
    248 
    249    @property
    250    def testdriver(self) -> Optional[bool]:
    251        return self._extras.get("testdriver")
    252 
    253    def to_json(self) -> Tuple[Optional[Text], List[Tuple[Text, Text]], Dict[Text, Any]]:  # type: ignore
    254        rel_url = None if self._url == self.path else self._url
    255        rv: Tuple[Optional[Text], List[Tuple[Text, Text]], Dict[Text, Any]] = (rel_url, self.references, {})
    256        extras = rv[-1]
    257        if self.timeout is not None:
    258            extras["timeout"] = self.timeout
    259        if self.viewport_size is not None:
    260            extras["viewport_size"] = self.viewport_size
    261        if self.dpi is not None:
    262            extras["dpi"] = self.dpi
    263        if self.fuzzy:
    264            extras["fuzzy"] = list(self.fuzzy.items())
    265        if self.testdriver:
    266            extras["testdriver"] = self.testdriver
    267        return rv
    268 
    269    @classmethod
    270    def from_json(cls,  # type: ignore
    271                  manifest: "Manifest",
    272                  path: Text,
    273                  obj: Tuple[Text, List[Tuple[Text, Text]], Dict[Any, Any]]
    274                  ) -> "RefTest":
    275        tests_root = manifest.tests_root
    276        assert tests_root is not None
    277        path = to_os_path(path)
    278        url, references, extras = obj
    279        return cls(tests_root,
    280                   path,
    281                   manifest.url_base,
    282                   url,
    283                   references,
    284                   **extras)
    285 
    286 
    287 class PrintRefTest(RefTest):
    288    __slots__ = ("references",)
    289 
    290    item_type = "print-reftest"
    291 
    292    @property
    293    def page_ranges(self) -> PageRanges:
    294        return cast(PageRanges, self._extras.get("page_ranges", {}))
    295 
    296    def to_json(self):  # type: ignore
    297        rv = super().to_json()
    298        if self.page_ranges:
    299            rv[-1]["page_ranges"] = self.page_ranges
    300        return rv
    301 
    302 
    303 class ManualTest(URLManifestItem):
    304    __slots__ = ()
    305 
    306    item_type = "manual"
    307 
    308 
    309 class ConformanceCheckerTest(URLManifestItem):
    310    __slots__ = ()
    311 
    312    item_type = "conformancechecker"
    313 
    314 
    315 class VisualTest(URLManifestItem):
    316    __slots__ = ()
    317 
    318    item_type = "visual"
    319 
    320 
    321 class CrashTest(URLManifestItem):
    322    __slots__ = ()
    323 
    324    item_type = "crashtest"
    325 
    326    @property
    327    def timeout(self) -> Optional[Text]:
    328        return None
    329 
    330    @property
    331    def testdriver(self) -> Optional[bool]:
    332        return self._extras.get("testdriver")
    333 
    334    def to_json(self):  # type: ignore
    335        rel_url, extras = super().to_json()
    336        if self.testdriver:
    337            extras["testdriver"] = self.testdriver
    338        return rel_url, extras
    339 
    340 
    341 class WebDriverSpecTest(URLManifestItem):
    342    __slots__ = ()
    343 
    344    item_type = "wdspec"
    345 
    346    @property
    347    def timeout(self) -> Optional[Text]:
    348        return self._extras.get("timeout")
    349 
    350    def to_json(self) -> Tuple[Optional[Text], Dict[Text, Any]]:
    351        rv = super().to_json()
    352        if self.timeout is not None:
    353            rv[-1]["timeout"] = self.timeout
    354        return rv
    355 
    356 
    357 class SupportFile(ManifestItem):
    358    __slots__ = ()
    359 
    360    item_type = "support"
    361 
    362    @property
    363    def id(self) -> Text:
    364        return self.path
    365 
    366 
    367 class SpecItem(ManifestItem):
    368    __slots__ = ("specs")
    369 
    370    item_type = "spec"
    371 
    372    def __init__(self,
    373                 tests_root: Text,
    374                 path: Text,
    375                 specs: List[Text]
    376                 ) -> None:
    377        super().__init__(tests_root, path)
    378        self.specs = specs
    379 
    380    @property
    381    def id(self) -> Text:
    382        return self.path
    383 
    384    def to_json(self) -> Tuple[Optional[Text], Dict[Text, Any]]:
    385        rv: Tuple[Optional[Text], Dict[Any, Any]] = (None, {})
    386        for i in range(len(self.specs)):
    387            spec_key = f"spec_link{i+1}"
    388            rv[-1][spec_key] = self.specs[i]
    389        return rv
    390 
    391    @classmethod
    392    def from_json(cls,
    393                  manifest: "Manifest",
    394                  path: Text,
    395                  obj: Any
    396                  ) -> "ManifestItem":
    397        """Not properly implemented and is not used."""
    398        return cls("/", "", [])