# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. import os import urlparse from wptmanifest.backends import static from wptmanifest.backends.static import ManifestItem import expected """Manifest structure used to store expected results of a test. Each manifest file is represented by an ExpectedManifest that has one or more TestNode children, one per test in the manifest. Each TestNode has zero or more SubtestNode children, one for each known subtest of the test. """ def data_cls_getter(output_node, visited_node): # visited_node is intentionally unused if output_node is None: return ExpectedManifest if isinstance(output_node, ExpectedManifest): return TestNode if isinstance(output_node, TestNode): return SubtestNode raise ValueError def disabled(node): """Boolean indicating whether the test is disabled""" try: return node.get("disabled") except KeyError: return None def tags(node): """Set of tags that have been applied to the test""" try: value = node.get("tags") if isinstance(value, (str, unicode)): return {value} return set(value) except KeyError: return set() def prefs(node): def value(ini_value): if isinstance(ini_value, (str, unicode)): return tuple(ini_value.split(":", 1)) else: return (ini_value, None) try: node_prefs = node.get("prefs") if type(node_prefs) in (str, unicode): prefs = {value(node_prefs)} rv = dict(value(item) for item in node_prefs) except KeyError: rv = {} return rv class ExpectedManifest(ManifestItem): def __init__(self, name, test_path, url_base): """Object representing all the tests in a particular manifest :param name: Name of the AST Node associated with this object. Should always be None since this should always be associated with the root node of the AST. :param test_path: Path of the test file associated with this manifest. :param url_base: Base url for serving the tests in this manifest """ if name is not None: raise ValueError("ExpectedManifest should represent the root node") if test_path is None: raise ValueError("ExpectedManifest requires a test path") if url_base is None: raise ValueError("ExpectedManifest requires a base url") ManifestItem.__init__(self, name) self.child_map = {} self.test_path = test_path self.url_base = url_base def append(self, child): """Add a test to the manifest""" ManifestItem.append(self, child) self.child_map[child.id] = child def _remove_child(self, child): del self.child_map[child.id] ManifestItem.remove_child(self, child) assert len(self.child_map) == len(self.children) def get_test(self, test_id): """Get a test from the manifest by ID :param test_id: ID of the test to return.""" return self.child_map.get(test_id) @property def url(self): return urlparse.urljoin(self.url_base, "/".join(self.test_path.split(os.path.sep))) @property def disabled(self): return disabled(self) @property def tags(self): return tags(self) @property def prefs(self): return prefs(self) class DirectoryManifest(ManifestItem): @property def disabled(self): return disabled(self) @property def tags(self): return tags(self) @property def prefs(self): return prefs(self) class TestNode(ManifestItem): def __init__(self, name): """Tree node associated with a particular test in a manifest :param name: name of the test""" assert name is not None ManifestItem.__init__(self, name) self.updated_expected = [] self.new_expected = [] self.subtests = {} self.default_status = None self._from_file = True @property def is_empty(self): required_keys = set(["type"]) if set(self._data.keys()) != required_keys: return False return all(child.is_empty for child in self.children) @property def test_type(self): return self.get("type") @property def id(self): return urlparse.urljoin(self.parent.url, self.name) @property def disabled(self): return disabled(self) @property def tags(self): return tags(self) @property def prefs(self): return prefs(self) def append(self, node): """Add a subtest to the current test :param node: AST Node associated with the subtest""" child = ManifestItem.append(self, node) self.subtests[child.name] = child def get_subtest(self, name): """Get the SubtestNode corresponding to a particular subtest, by name :param name: Name of the node to return""" if name in self.subtests: return self.subtests[name] return None class SubtestNode(TestNode): def __init__(self, name): """Tree node associated with a particular subtest in a manifest :param name: name of the subtest""" TestNode.__init__(self, name) @property def is_empty(self): if self._data: return False return True def get_manifest(metadata_root, test_path, url_base, run_info): """Get the ExpectedManifest for a particular test path, or None if there is no metadata stored for that test path. :param metadata_root: Absolute path to the root of the metadata directory :param test_path: Path to the test(s) relative to the test root :param url_base: Base url for serving the tests in this manifest :param run_info: Dictionary of properties of the test run for which the expectation values should be computed. """ manifest_path = expected.expected_path(metadata_root, test_path) try: with open(manifest_path) as f: return static.compile(f, run_info, data_cls_getter=data_cls_getter, test_path=test_path, url_base=url_base) except IOError: return None def get_dir_manifest(metadata_root, path, run_info): """Get the ExpectedManifest for a particular test path, or None if there is no metadata stored for that test path. :param metadata_root: Absolute path to the root of the metadata directory :param path: Path to the ini file relative to the metadata root :param run_info: Dictionary of properties of the test run for which the expectation values should be computed. """ full_path = os.path.join(metadata_root, path) try: with open(full_path) as f: return static.compile(f, run_info, data_cls_getter=lambda x,y: DirectoryManifest) except IOError: return None