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
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
)
30 assert issubclass(inst
, ManifestItem
)
32 item_type
= cast(str, inst
.item_type
)
34 assert isinstance(inst
.item_type
, str)
35 item_type
= inst
.item_type
37 item_types
[item_type
] = 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
53 """The test's id (usually its url)"""
64 # type: () -> Tuple[Text, ...]
65 return tuple(self
.path
.split(os
.path
.sep
))
68 # type: () -> Hashable
69 """A unique identifier for the test"""
70 return (self
.item_type
, self
.id)
72 def __eq__(self
, other
):
74 if not hasattr(other
, "key"):
76 return bool(self
.key() == other
.key())
80 return hash(self
.key())
84 return f
"<{self.__module__}.{self.__class__.__name__} id={self.id!r}, path={self.path!r}>"
87 # type: () -> Tuple[Any, ...]
92 manifest
, # type: Manifest
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")
107 tests_root
, # type: Text
109 url_base
, # type: Text
110 url
, # type: Optional[Text]
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] != "/"
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", [])))
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
== "/":
136 return urljoin(self
.url_base
, rel_url
)
141 return "https" in self
._flags
or "serviceworker" in self
._flags
or "serviceworker-module" in self
._flags
146 return "h2" in self
._flags
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
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]]
163 manifest
, # type: Manifest
165 obj
# type: Tuple[Text, Dict[Any, Any]]
167 # type: (...) -> URLManifestItem
168 path
= to_os_path(path
)
170 tests_root
= manifest
.tests_root
171 assert tests_root
is not None
172 return cls(tests_root
,
179 class TestharnessTest(URLManifestItem
):
182 item_type
= "testharness"
186 # type: () -> Optional[Text]
187 return self
._extras
.get("timeout")
191 # type: () -> Optional[Text]
192 return self
._extras
.get("pac")
195 def testdriver(self
):
196 # type: () -> Optional[Text]
197 return self
._extras
.get("testdriver")
201 # type: () -> Optional[Text]
202 return self
._extras
.get("jsshell")
205 def script_metadata(self
):
206 # type: () -> Optional[List[Tuple[Text, Text]]]
207 return self
._extras
.get("script_metadata")
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
217 rv
[-1]["testdriver"] = self
.testdriver
219 rv
[-1]["jsshell"] = True
220 if self
.script_metadata
:
221 rv
[-1]["script_metadata"] = [(k
, v
) for (k
,v
) in self
.script_metadata
]
225 class RefTest(URLManifestItem
):
226 __slots__
= ("references",)
228 item_type
= "reftest"
231 tests_root
, # type: Text
233 url_base
, # type: Text
234 url
, # type: Optional[Text]
235 references
=None, # type: Optional[List[Tuple[Text, Text]]]
238 super().__init
__(tests_root
, path
, url_base
, url
, **extras
)
239 if references
is None:
240 self
.references
= [] # type: List[Tuple[Text, Text]]
242 self
.references
= references
246 # type: () -> Optional[Text]
247 return self
._extras
.get("timeout")
250 def viewport_size(self
):
251 # type: () -> Optional[Text]
252 return self
._extras
.get("viewport_size")
256 # type: () -> Optional[Text]
257 return self
._extras
.get("dpi")
262 fuzzy
= self
._extras
.get("fuzzy", {}) # type: Union[Fuzzy, List[Tuple[Optional[Sequence[Text]], List[int]]]]
263 if not isinstance(fuzzy
, list):
266 rv
= {} # type: Fuzzy
267 for k
, v
in fuzzy
: # type: Tuple[Optional[Sequence[Text]], List[int]]
269 key
= None # type: Optional[Tuple[Text, Text, Text]]
271 # mypy types this as Tuple[Text, ...]
273 key
= tuple(k
) # type: ignore
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]]
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
289 extras
["fuzzy"] = list(self
.fuzzy
.items())
293 def from_json(cls
, # type: ignore
294 manifest
, # type: Manifest
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
,
311 class PrintRefTest(RefTest
):
312 __slots__
= ("references",)
314 item_type
= "print-reftest"
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()
324 rv
[-1]["page_ranges"] = self
.page_ranges
328 class ManualTest(URLManifestItem
):
334 class ConformanceCheckerTest(URLManifestItem
):
337 item_type
= "conformancechecker"
340 class VisualTest(URLManifestItem
):
346 class CrashTest(URLManifestItem
):
349 item_type
= "crashtest"
353 # type: () -> Optional[Text]
357 class WebDriverSpecTest(URLManifestItem
):
364 # type: () -> Optional[Text]
365 return self
._extras
.get("timeout")
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
375 class SupportFile(ManifestItem
):
378 item_type
= "support"