Bug 1826564 [wpt PR 39394] - Update mypy, a=testonly
[gecko.git] / testing / web-platform / tests / tools / manifest / item.py
blobf798ec2ae8d7fed1f39b1dee478e7645f0fdb4bf
1 import os.path
2 from inspect import isabstract
3 from urllib.parse import urljoin, urlparse, parse_qs
4 from abc import ABCMeta, abstractproperty
6 from .utils import to_os_path
8 MYPY = False
9 if MYPY:
10 # MYPY is set to True when run under Mypy.
11 from typing import Any, Dict, Hashable, List, Optional, Sequence, Text, Tuple, Type, Union, cast
12 from .manifest import Manifest
13 Fuzzy = Dict[Optional[Tuple[str, str, str]], List[int]]
14 PageRanges = Dict[str, List[int]]
16 item_types = {} # type: Dict[str, Type[ManifestItem]]
19 class ManifestItemMeta(ABCMeta):
20 """Custom metaclass that registers all the subclasses in the
21 item_types dictionary according to the value of their item_type
22 attribute, and otherwise behaves like an ABCMeta."""
24 def __new__(cls, name, bases, attrs):
25 # type: (Type[ManifestItemMeta], str, Tuple[type], Dict[str, Any]) -> ManifestItemMeta
26 inst = super().__new__(cls, name, bases, attrs)
27 if isabstract(inst):
28 return inst
30 assert issubclass(inst, ManifestItem)
31 if MYPY:
32 item_type = cast(str, inst.item_type)
33 else:
34 assert isinstance(inst.item_type, str)
35 item_type = inst.item_type
37 item_types[item_type] = inst
39 return inst
42 class ManifestItem(metaclass=ManifestItemMeta):
43 __slots__ = ("_tests_root", "path")
45 def __init__(self, tests_root, path):
46 # type: (Text, Text) -> None
47 self._tests_root = tests_root
48 self.path = path
50 @abstractproperty
51 def id(self):
52 # type: () -> Text
53 """The test's id (usually its url)"""
54 pass
56 @abstractproperty
57 def item_type(self):
58 # type: () -> str
59 """The item's type"""
60 pass
62 @property
63 def path_parts(self):
64 # type: () -> Tuple[Text, ...]
65 return tuple(self.path.split(os.path.sep))
67 def key(self):
68 # type: () -> Hashable
69 """A unique identifier for the test"""
70 return (self.item_type, self.id)
72 def __eq__(self, other):
73 # type: (Any) -> bool
74 if not hasattr(other, "key"):
75 return False
76 return bool(self.key() == other.key())
78 def __hash__(self):
79 # type: () -> int
80 return hash(self.key())
82 def __repr__(self):
83 # type: () -> str
84 return f"<{self.__module__}.{self.__class__.__name__} id={self.id!r}, path={self.path!r}>"
86 def to_json(self):
87 # type: () -> Tuple[Any, ...]
88 return ()
90 @classmethod
91 def from_json(cls,
92 manifest, # type: Manifest
93 path, # type: Text
94 obj # type: Any
96 # type: (...) -> ManifestItem
97 path = to_os_path(path)
98 tests_root = manifest.tests_root
99 assert tests_root is not None
100 return cls(tests_root, path)
103 class URLManifestItem(ManifestItem):
104 __slots__ = ("url_base", "_url", "_extras", "_flags")
106 def __init__(self,
107 tests_root, # type: Text
108 path, # type: Text
109 url_base, # type: Text
110 url, # type: Optional[Text]
111 **extras # type: Any
113 # type: (...) -> None
114 super().__init__(tests_root, path)
115 assert url_base[0] == "/"
116 self.url_base = url_base
117 assert url is None or url[0] != "/"
118 self._url = url
119 self._extras = extras
120 parsed_url = urlparse(self.url)
121 self._flags = (set(parsed_url.path.rsplit("/", 1)[1].split(".")[1:-1]) |
122 set(parse_qs(parsed_url.query).get("wpt_flags", [])))
124 @property
125 def id(self):
126 # type: () -> Text
127 return self.url
129 @property
130 def url(self):
131 # type: () -> Text
132 rel_url = self._url or self.path.replace(os.path.sep, "/")
133 # we can outperform urljoin, because we know we just have path relative URLs
134 if self.url_base == "/":
135 return "/" + rel_url
136 return urljoin(self.url_base, rel_url)
138 @property
139 def https(self):
140 # type: () -> bool
141 return "https" in self._flags or "serviceworker" in self._flags or "serviceworker-module" in self._flags
143 @property
144 def h2(self):
145 # type: () -> bool
146 return "h2" in self._flags
148 @property
149 def subdomain(self):
150 # type: () -> bool
151 # Note: this is currently hard-coded to check for `www`, rather than
152 # all possible valid subdomains. It can be extended if needed.
153 return "www" in self._flags
155 def to_json(self):
156 # type: () -> Tuple[Optional[Text], Dict[Any, Any]]
157 rel_url = None if self._url == self.path.replace(os.path.sep, "/") else self._url
158 rv = (rel_url, {}) # type: Tuple[Optional[Text], Dict[Any, Any]]
159 return rv
161 @classmethod
162 def from_json(cls,
163 manifest, # type: Manifest
164 path, # type: Text
165 obj # type: Tuple[Text, Dict[Any, Any]]
167 # type: (...) -> URLManifestItem
168 path = to_os_path(path)
169 url, extras = obj
170 tests_root = manifest.tests_root
171 assert tests_root is not None
172 return cls(tests_root,
173 path,
174 manifest.url_base,
175 url,
176 **extras)
179 class TestharnessTest(URLManifestItem):
180 __slots__ = ()
182 item_type = "testharness"
184 @property
185 def timeout(self):
186 # type: () -> Optional[Text]
187 return self._extras.get("timeout")
189 @property
190 def pac(self):
191 # type: () -> Optional[Text]
192 return self._extras.get("pac")
194 @property
195 def testdriver(self):
196 # type: () -> Optional[Text]
197 return self._extras.get("testdriver")
199 @property
200 def jsshell(self):
201 # type: () -> Optional[Text]
202 return self._extras.get("jsshell")
204 @property
205 def script_metadata(self):
206 # type: () -> Optional[List[Tuple[Text, Text]]]
207 return self._extras.get("script_metadata")
209 def to_json(self):
210 # type: () -> Tuple[Optional[Text], Dict[Text, Any]]
211 rv = super().to_json()
212 if self.timeout is not None:
213 rv[-1]["timeout"] = self.timeout
214 if self.pac is not None:
215 rv[-1]["pac"] = self.pac
216 if self.testdriver:
217 rv[-1]["testdriver"] = self.testdriver
218 if self.jsshell:
219 rv[-1]["jsshell"] = True
220 if self.script_metadata:
221 rv[-1]["script_metadata"] = [(k, v) for (k,v) in self.script_metadata]
222 return rv
225 class RefTest(URLManifestItem):
226 __slots__ = ("references",)
228 item_type = "reftest"
230 def __init__(self,
231 tests_root, # type: Text
232 path, # type: Text
233 url_base, # type: Text
234 url, # type: Optional[Text]
235 references=None, # type: Optional[List[Tuple[Text, Text]]]
236 **extras # type: Any
238 super().__init__(tests_root, path, url_base, url, **extras)
239 if references is None:
240 self.references = [] # type: List[Tuple[Text, Text]]
241 else:
242 self.references = references
244 @property
245 def timeout(self):
246 # type: () -> Optional[Text]
247 return self._extras.get("timeout")
249 @property
250 def viewport_size(self):
251 # type: () -> Optional[Text]
252 return self._extras.get("viewport_size")
254 @property
255 def dpi(self):
256 # type: () -> Optional[Text]
257 return self._extras.get("dpi")
259 @property
260 def fuzzy(self):
261 # type: () -> Fuzzy
262 fuzzy = self._extras.get("fuzzy", {}) # type: Union[Fuzzy, List[Tuple[Optional[Sequence[Text]], List[int]]]]
263 if not isinstance(fuzzy, list):
264 return fuzzy
266 rv = {} # type: Fuzzy
267 for k, v in fuzzy: # type: Tuple[Optional[Sequence[Text]], List[int]]
268 if k is None:
269 key = None # type: Optional[Tuple[Text, Text, Text]]
270 else:
271 # mypy types this as Tuple[Text, ...]
272 assert len(k) == 3
273 key = tuple(k) # type: ignore
274 rv[key] = v
275 return rv
277 def to_json(self): # type: ignore
278 # type: () -> Tuple[Optional[Text], List[Tuple[Text, Text]], Dict[Text, Any]]
279 rel_url = None if self._url == self.path else self._url
280 rv = (rel_url, self.references, {}) # type: Tuple[Optional[Text], List[Tuple[Text, Text]], Dict[Text, Any]]
281 extras = rv[-1]
282 if self.timeout is not None:
283 extras["timeout"] = self.timeout
284 if self.viewport_size is not None:
285 extras["viewport_size"] = self.viewport_size
286 if self.dpi is not None:
287 extras["dpi"] = self.dpi
288 if self.fuzzy:
289 extras["fuzzy"] = list(self.fuzzy.items())
290 return rv
292 @classmethod
293 def from_json(cls, # type: ignore
294 manifest, # type: Manifest
295 path, # type: Text
296 obj # type: Tuple[Text, List[Tuple[Text, Text]], Dict[Any, Any]]
298 # type: (...) -> RefTest
299 tests_root = manifest.tests_root
300 assert tests_root is not None
301 path = to_os_path(path)
302 url, references, extras = obj
303 return cls(tests_root,
304 path,
305 manifest.url_base,
306 url,
307 references,
308 **extras)
311 class PrintRefTest(RefTest):
312 __slots__ = ("references",)
314 item_type = "print-reftest"
316 @property
317 def page_ranges(self):
318 # type: () -> PageRanges
319 return self._extras.get("page_ranges", {})
321 def to_json(self): # type: ignore
322 rv = super().to_json()
323 if self.page_ranges:
324 rv[-1]["page_ranges"] = self.page_ranges
325 return rv
328 class ManualTest(URLManifestItem):
329 __slots__ = ()
331 item_type = "manual"
334 class ConformanceCheckerTest(URLManifestItem):
335 __slots__ = ()
337 item_type = "conformancechecker"
340 class VisualTest(URLManifestItem):
341 __slots__ = ()
343 item_type = "visual"
346 class CrashTest(URLManifestItem):
347 __slots__ = ()
349 item_type = "crashtest"
351 @property
352 def timeout(self):
353 # type: () -> Optional[Text]
354 return None
357 class WebDriverSpecTest(URLManifestItem):
358 __slots__ = ()
360 item_type = "wdspec"
362 @property
363 def timeout(self):
364 # type: () -> Optional[Text]
365 return self._extras.get("timeout")
367 def to_json(self):
368 # type: () -> Tuple[Optional[Text], Dict[Text, Any]]
369 rv = super().to_json()
370 if self.timeout is not None:
371 rv[-1]["timeout"] = self.timeout
372 return rv
375 class SupportFile(ManifestItem):
376 __slots__ = ()
378 item_type = "support"
380 @property
381 def id(self):
382 # type: () -> Text
383 return self.path