Index: third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/manifest/sourcefile.py |
diff --git a/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/manifest/sourcefile.py b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/manifest/sourcefile.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..499518b9e9db7a2aaf520e672ef6c1533d845f51 |
--- /dev/null |
+++ b/third_party/WebKit/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/manifest/sourcefile.py |
@@ -0,0 +1,481 @@ |
+import hashlib |
+import re |
+import os |
+from six.moves.urllib.parse import urljoin |
+from fnmatch import fnmatch |
+try: |
+ from xml.etree import cElementTree as ElementTree |
+except ImportError: |
+ from xml.etree import ElementTree |
+ |
+import html5lib |
+ |
+from . import XMLParser |
+from .item import Stub, ManualTest, WebdriverSpecTest, RefTestNode, RefTest, TestharnessTest, SupportFile, ConformanceCheckerTest, VisualTest |
+from .utils import rel_path_to_url, ContextManagerBytesIO, cached_property |
+ |
+wd_pattern = "*.py" |
+ |
+reference_file_re = re.compile(r'(^|[\-_])(not)?ref[0-9]*([\-_]|$)') |
+ |
+def replace_end(s, old, new): |
+ """ |
+ Given a string `s` that ends with `old`, replace that occurrence of `old` |
+ with `new`. |
+ """ |
+ assert s.endswith(old) |
+ return s[:-len(old)] + new |
+ |
+ |
+class SourceFile(object): |
+ parsers = {"html":lambda x:html5lib.parse(x, treebuilder="etree"), |
+ "xhtml":lambda x:ElementTree.parse(x, XMLParser.XMLParser()), |
+ "svg":lambda x:ElementTree.parse(x, XMLParser.XMLParser())} |
+ |
+ root_dir_non_test = set(["common", |
+ "work-in-progress"]) |
+ |
+ dir_non_test = set(["resources", |
+ "support", |
+ "tools"]) |
+ |
+ dir_path_non_test = {("css21", "archive")} |
+ |
+ def __init__(self, tests_root, rel_path, url_base, contents=None): |
+ """Object representing a file in a source tree. |
+ |
+ :param tests_root: Path to the root of the source tree |
+ :param rel_path: File path relative to tests_root |
+ :param url_base: Base URL used when converting file paths to urls |
+ :param contents: Byte array of the contents of the file or ``None``. |
+ """ |
+ |
+ self.tests_root = tests_root |
+ self.rel_path = os.path.normcase(rel_path) # does slash normalization on Windows |
+ self.url_base = url_base |
+ self.contents = contents |
+ |
+ self.dir_path, self.filename = os.path.split(self.rel_path) |
+ self.name, self.ext = os.path.splitext(self.filename) |
+ |
+ self.type_flag = None |
+ if "-" in self.name: |
+ self.type_flag = self.name.rsplit("-", 1)[1].split(".")[0] |
+ |
+ self.meta_flags = self.name.split(".")[1:] |
+ |
+ self.items_cache = None |
+ |
+ def __getstate__(self): |
+ # Remove computed properties if we pickle this class |
+ rv = self.__dict__.copy() |
+ |
+ if "__cached_properties__" in rv: |
+ cached_properties = rv["__cached_properties__"] |
+ for key in rv.keys(): |
+ if key in cached_properties: |
+ del rv[key] |
+ del rv["__cached_properties__"] |
+ return rv |
+ |
+ def name_prefix(self, prefix): |
+ """Check if the filename starts with a given prefix |
+ |
+ :param prefix: The prefix to check""" |
+ return self.name.startswith(prefix) |
+ |
+ def is_dir(self): |
+ """Return whether this file represents a directory.""" |
+ if self.contents is not None: |
+ return False |
+ |
+ return os.path.isdir(self.rel_path) |
+ |
+ def open(self): |
+ """ |
+ Return either |
+ * the contents specified in the constructor, if any; |
+ * a File object opened for reading the file contents. |
+ """ |
+ |
+ if self.contents is not None: |
+ file_obj = ContextManagerBytesIO(self.contents) |
+ else: |
+ file_obj = open(self.path, 'rb') |
+ return file_obj |
+ |
+ @cached_property |
+ def path(self): |
+ return os.path.join(self.tests_root, self.rel_path) |
+ |
+ @cached_property |
+ def url(self): |
+ return rel_path_to_url(self.rel_path, self.url_base) |
+ |
+ @cached_property |
+ def hash(self): |
+ with self.open() as f: |
+ return hashlib.sha1(f.read()).hexdigest() |
+ |
+ def in_non_test_dir(self): |
+ if self.dir_path == "": |
+ return True |
+ |
+ parts = self.dir_path.split(os.path.sep) |
+ |
+ if parts[0] in self.root_dir_non_test: |
+ return True |
+ elif any(item in self.dir_non_test for item in parts): |
+ return True |
+ else: |
+ for path in self.dir_path_non_test: |
+ if parts[:len(path)] == list(path): |
+ return True |
+ return False |
+ |
+ def in_conformance_checker_dir(self): |
+ return (self.dir_path == "conformance-checkers" or |
+ self.dir_path.startswith("conformance-checkers" + os.path.sep)) |
+ |
+ @property |
+ def name_is_non_test(self): |
+ """Check if the file name matches the conditions for the file to |
+ be a non-test file""" |
+ return (self.is_dir() or |
+ self.name_prefix("MANIFEST") or |
+ self.filename.startswith(".") or |
+ self.in_non_test_dir()) |
+ |
+ @property |
+ def name_is_conformance(self): |
+ return (self.in_conformance_checker_dir() and |
+ self.type_flag in ("is-valid", "no-valid")) |
+ |
+ @property |
+ def name_is_conformance_support(self): |
+ return self.in_conformance_checker_dir() |
+ |
+ @property |
+ def name_is_stub(self): |
+ """Check if the file name matches the conditions for the file to |
+ be a stub file""" |
+ return self.name_prefix("stub-") |
+ |
+ @property |
+ def name_is_manual(self): |
+ """Check if the file name matches the conditions for the file to |
+ be a manual test file""" |
+ return self.type_flag == "manual" |
+ |
+ @property |
+ def name_is_visual(self): |
+ """Check if the file name matches the conditions for the file to |
+ be a visual test file""" |
+ return self.type_flag == "visual" |
+ |
+ @property |
+ def name_is_multi_global(self): |
+ """Check if the file name matches the conditions for the file to |
+ be a multi-global js test file""" |
+ return "any" in self.meta_flags and self.ext == ".js" |
+ |
+ @property |
+ def name_is_worker(self): |
+ """Check if the file name matches the conditions for the file to |
+ be a worker js test file""" |
+ return "worker" in self.meta_flags and self.ext == ".js" |
+ |
+ @property |
+ def name_is_webdriver(self): |
+ """Check if the file name matches the conditions for the file to |
+ be a webdriver spec test file""" |
+ # wdspec tests are in subdirectories of /webdriver excluding __init__.py |
+ # files. |
+ rel_dir_tree = self.rel_path.split(os.path.sep) |
+ return (rel_dir_tree[0] == "webdriver" and |
+ len(rel_dir_tree) > 1 and |
+ self.filename != "__init__.py" and |
+ fnmatch(self.filename, wd_pattern)) |
+ |
+ @property |
+ def name_is_reference(self): |
+ """Check if the file name matches the conditions for the file to |
+ be a reference file (not a reftest)""" |
+ return "/reference/" in self.url or "/reftest/" in self.url or bool(reference_file_re.search(self.name)) |
+ |
+ @property |
+ def markup_type(self): |
+ """Return the type of markup contained in a file, based on its extension, |
+ or None if it doesn't contain markup""" |
+ ext = self.ext |
+ |
+ if not ext: |
+ return None |
+ if ext[0] == ".": |
+ ext = ext[1:] |
+ if ext in ["html", "htm"]: |
+ return "html" |
+ if ext in ["xhtml", "xht", "xml"]: |
+ return "xhtml" |
+ if ext == "svg": |
+ return "svg" |
+ return None |
+ |
+ @cached_property |
+ def root(self): |
+ """Return an ElementTree Element for the root node of the file if it contains |
+ markup, or None if it does not""" |
+ if not self.markup_type: |
+ return None |
+ |
+ parser = self.parsers[self.markup_type] |
+ |
+ with self.open() as f: |
+ try: |
+ tree = parser(f) |
+ except Exception: |
+ return None |
+ |
+ if hasattr(tree, "getroot"): |
+ root = tree.getroot() |
+ else: |
+ root = tree |
+ |
+ return root |
+ |
+ @cached_property |
+ def timeout_nodes(self): |
+ """List of ElementTree Elements corresponding to nodes in a test that |
+ specify timeouts""" |
+ return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='timeout']") |
+ |
+ @cached_property |
+ def timeout(self): |
+ """The timeout of a test or reference file. "long" if the file has an extended timeout |
+ or None otherwise""" |
+ if self.root is None: |
+ return |
+ |
+ if self.timeout_nodes: |
+ timeout_str = self.timeout_nodes[0].attrib.get("content", None) |
+ if timeout_str and timeout_str.lower() == "long": |
+ return timeout_str |
+ |
+ @cached_property |
+ def viewport_nodes(self): |
+ """List of ElementTree Elements corresponding to nodes in a test that |
+ specify viewport sizes""" |
+ return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='viewport-size']") |
+ |
+ @cached_property |
+ def viewport_size(self): |
+ """The viewport size of a test or reference file""" |
+ if self.root is None: |
+ return None |
+ |
+ if not self.viewport_nodes: |
+ return None |
+ |
+ return self.viewport_nodes[0].attrib.get("content", None) |
+ |
+ @cached_property |
+ def dpi_nodes(self): |
+ """List of ElementTree Elements corresponding to nodes in a test that |
+ specify device pixel ratios""" |
+ return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='device-pixel-ratio']") |
+ |
+ @cached_property |
+ def dpi(self): |
+ """The device pixel ratio of a test or reference file""" |
+ if self.root is None: |
+ return None |
+ |
+ if not self.dpi_nodes: |
+ return None |
+ |
+ return self.dpi_nodes[0].attrib.get("content", None) |
+ |
+ @cached_property |
+ def testharness_nodes(self): |
+ """List of ElementTree Elements corresponding to nodes representing a |
+ testharness.js script""" |
+ return self.root.findall(".//{http://www.w3.org/1999/xhtml}script[@src='/resources/testharness.js']") |
+ |
+ @cached_property |
+ def content_is_testharness(self): |
+ """Boolean indicating whether the file content represents a |
+ testharness.js test""" |
+ if self.root is None: |
+ return None |
+ return bool(self.testharness_nodes) |
+ |
+ @cached_property |
+ def variant_nodes(self): |
+ """List of ElementTree Elements corresponding to nodes representing a |
+ test variant""" |
+ return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='variant']") |
+ |
+ @cached_property |
+ def test_variants(self): |
+ rv = [] |
+ for element in self.variant_nodes: |
+ if "content" in element.attrib: |
+ variant = element.attrib["content"] |
+ assert variant == "" or variant[0] in ["#", "?"] |
+ rv.append(variant) |
+ |
+ if not rv: |
+ rv = [""] |
+ |
+ return rv |
+ |
+ @cached_property |
+ def reftest_nodes(self): |
+ """List of ElementTree Elements corresponding to nodes representing a |
+ to a reftest <link>""" |
+ if self.root is None: |
+ return [] |
+ |
+ match_links = self.root.findall(".//{http://www.w3.org/1999/xhtml}link[@rel='match']") |
+ mismatch_links = self.root.findall(".//{http://www.w3.org/1999/xhtml}link[@rel='mismatch']") |
+ return match_links + mismatch_links |
+ |
+ @cached_property |
+ def references(self): |
+ """List of (ref_url, relation) tuples for any reftest references specified in |
+ the file""" |
+ rv = [] |
+ rel_map = {"match": "==", "mismatch": "!="} |
+ for item in self.reftest_nodes: |
+ if "href" in item.attrib: |
+ ref_url = urljoin(self.url, item.attrib["href"]) |
+ ref_type = rel_map[item.attrib["rel"]] |
+ rv.append((ref_url, ref_type)) |
+ return rv |
+ |
+ @cached_property |
+ def content_is_ref_node(self): |
+ """Boolean indicating whether the file is a non-leaf node in a reftest |
+ graph (i.e. if it contains any <link rel=[mis]match>""" |
+ return bool(self.references) |
+ |
+ @cached_property |
+ def css_flag_nodes(self): |
+ """List of ElementTree Elements corresponding to nodes representing a |
+ flag <meta>""" |
+ if self.root is None: |
+ return [] |
+ return self.root.findall(".//{http://www.w3.org/1999/xhtml}meta[@name='flags']") |
+ |
+ @cached_property |
+ def css_flags(self): |
+ """Set of flags specified in the file""" |
+ rv = set() |
+ for item in self.css_flag_nodes: |
+ if "content" in item.attrib: |
+ for flag in item.attrib["content"].split(): |
+ rv.add(flag) |
+ return rv |
+ |
+ @cached_property |
+ def content_is_css_manual(self): |
+ """Boolean indicating whether the file content represents a |
+ CSS WG-style manual test""" |
+ if self.root is None: |
+ return None |
+ # return True if the intersection between the two sets is non-empty |
+ return bool(self.css_flags & {"animated", "font", "history", "interact", "paged", "speech", "userstyle"}) |
+ |
+ @cached_property |
+ def spec_link_nodes(self): |
+ """List of ElementTree Elements corresponding to nodes representing a |
+ <link rel=help>, used to point to specs""" |
+ if self.root is None: |
+ return [] |
+ return self.root.findall(".//{http://www.w3.org/1999/xhtml}link[@rel='help']") |
+ |
+ @cached_property |
+ def spec_links(self): |
+ """Set of spec links specified in the file""" |
+ rv = set() |
+ for item in self.spec_link_nodes: |
+ if "href" in item.attrib: |
+ rv.add(item.attrib["href"]) |
+ return rv |
+ |
+ @cached_property |
+ def content_is_css_visual(self): |
+ """Boolean indicating whether the file content represents a |
+ CSS WG-style manual test""" |
+ if self.root is None: |
+ return None |
+ return bool(self.ext in {'.xht', '.html', '.xhtml', '.htm', '.xml', '.svg'} and |
+ self.spec_links) |
+ |
+ @property |
+ def type(self): |
+ rv, _ = self.manifest_items() |
+ return rv |
+ |
+ def manifest_items(self): |
+ """List of manifest items corresponding to the file. There is typically one |
+ per test, but in the case of reftests a node may have corresponding manifest |
+ items without being a test itself.""" |
+ |
+ if self.items_cache: |
+ return self.items_cache |
+ |
+ if self.name_is_non_test: |
+ rv = "support", [SupportFile(self)] |
+ |
+ elif self.name_is_stub: |
+ rv = Stub.item_type, [Stub(self, self.url)] |
+ |
+ elif self.name_is_manual: |
+ rv = ManualTest.item_type, [ManualTest(self, self.url)] |
+ |
+ elif self.name_is_conformance: |
+ rv = ConformanceCheckerTest.item_type, [ConformanceCheckerTest(self, self.url)] |
+ |
+ elif self.name_is_conformance_support: |
+ rv = "support", [SupportFile(self)] |
+ |
+ elif self.name_is_visual: |
+ rv = VisualTest.item_type, [VisualTest(self, self.url)] |
+ |
+ elif self.name_is_multi_global: |
+ rv = TestharnessTest.item_type, [ |
+ TestharnessTest(self, replace_end(self.url, ".any.js", ".any.html")), |
+ TestharnessTest(self, replace_end(self.url, ".any.js", ".any.worker.html")), |
+ ] |
+ |
+ elif self.name_is_worker: |
+ rv = (TestharnessTest.item_type, |
+ [TestharnessTest(self, replace_end(self.url, ".worker.js", ".worker.html"))]) |
+ |
+ elif self.name_is_webdriver: |
+ rv = WebdriverSpecTest.item_type, [WebdriverSpecTest(self, self.url)] |
+ |
+ elif self.content_is_css_manual and not self.name_is_reference: |
+ rv = ManualTest.item_type, [ManualTest(self, self.url)] |
+ |
+ elif self.content_is_testharness: |
+ rv = TestharnessTest.item_type, [] |
+ for variant in self.test_variants: |
+ url = self.url + variant |
+ rv[1].append(TestharnessTest(self, url, timeout=self.timeout)) |
+ |
+ elif self.content_is_ref_node: |
+ rv = (RefTestNode.item_type, |
+ [RefTestNode(self, self.url, self.references, timeout=self.timeout, |
+ viewport_size=self.viewport_size, dpi=self.dpi)]) |
+ |
+ elif self.content_is_css_visual and not self.name_is_reference: |
+ rv = VisualTest.item_type, [VisualTest(self, self.url)] |
+ |
+ else: |
+ rv = "support", [SupportFile(self)] |
+ |
+ self.items_cache = rv |
+ |
+ return rv |