summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/tools/manifest/sourcefile.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/tools/manifest/sourcefile.py')
-rw-r--r--testing/web-platform/tests/tools/manifest/sourcefile.py366
1 files changed, 366 insertions, 0 deletions
diff --git a/testing/web-platform/tests/tools/manifest/sourcefile.py b/testing/web-platform/tests/tools/manifest/sourcefile.py
new file mode 100644
index 000000000..44a462707
--- /dev/null
+++ b/testing/web-platform/tests/tools/manifest/sourcefile.py
@@ -0,0 +1,366 @@
+import imp
+import os
+import re
+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 vcs
+from .item import Stub, ManualTest, WebdriverSpecTest, RefTest, TestharnessTest
+from .utils import rel_path_to_url, is_blacklisted, ContextManagerBytesIO, cached_property
+
+wd_pattern = "*.py"
+meta_re = re.compile("//\s*<meta>\s*(\w*)=(.*)$")
+
+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":ElementTree.parse,
+ "svg":ElementTree.parse}
+
+ def __init__(self, tests_root, rel_path, url_base, use_committed=False,
+ 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 use_committed: Work with the last committed version of the file
+ rather than the on-disk version.
+ :param contents: Byte array of the contents of the file or ``None``.
+ """
+
+ assert not (use_committed and contents is not None)
+
+ self.tests_root = tests_root
+ self.rel_path = rel_path
+ self.url_base = url_base
+ self.use_committed = use_committed
+ self.contents = contents
+
+ self.url = rel_path_to_url(rel_path, url_base)
+ self.path = os.path.join(tests_root, rel_path)
+
+ self.dir_path, self.filename = os.path.split(self.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:]
+
+ 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;
+ * the contents of the file when last committed, if use_committed is true; or
+ * a File object opened for reading the file contents.
+ """
+
+ if self.contents is not None:
+ file_obj = ContextManagerBytesIO(self.contents)
+ elif self.use_committed:
+ git = vcs.get_git_func(os.path.dirname(__file__))
+ blob = git("show", "HEAD:%s" % self.rel_path)
+ file_obj = ContextManagerBytesIO(blob)
+ else:
+ file_obj = open(self.path, 'rb')
+ return file_obj
+
+ @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
+ is_blacklisted(self.url))
+
+ @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_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 self.type_flag in ("ref", "notref")
+
+ @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.name_is_worker:
+ with self.open() as f:
+ for line in f:
+ m = meta_re.match(line)
+ if m and m.groups()[0] == "timeout":
+ if m.groups()[1].lower() == "long":
+ return "long"
+ return
+
+ if not self.root:
+ 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 not self.root:
+ 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 not self.root:
+ 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 not self.root:
+ 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 not self.root:
+ 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)
+
+ 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.name_is_non_test:
+ rv = []
+
+ elif self.name_is_stub:
+ rv = [Stub(self, self.url)]
+
+ elif self.name_is_manual:
+ rv = [ManualTest(self, self.url)]
+
+ elif self.name_is_multi_global:
+ rv = [
+ TestharnessTest(self, replace_end(self.url, ".any.js", ".any.html")),
+ TestharnessTest(self, replace_end(self.url, ".any.js", ".any.worker")),
+ ]
+
+ elif self.name_is_worker:
+ rv = [TestharnessTest(self, replace_end(self.url, ".worker.js", ".worker"),
+ timeout=self.timeout)]
+
+ elif self.name_is_webdriver:
+ rv = [WebdriverSpecTest(self, self.url)]
+
+ elif self.content_is_testharness:
+ rv = []
+ for variant in self.test_variants:
+ url = self.url + variant
+ rv.append(TestharnessTest(self, url, timeout=self.timeout))
+
+ elif self.content_is_ref_node:
+ rv = [RefTest(self, self.url, self.references, timeout=self.timeout,
+ viewport_size=self.viewport_size, dpi=self.dpi)]
+
+ else:
+ # If nothing else it's a helper file, which we don't have a specific type for
+ rv = []
+
+ return rv