testing/web-platform/tests/tools/wptrunner/wptrunner/manifestinclude.py
author James Graham <james@hoppipolla.co.uk>
Thu, 01 Jun 2017 19:01:01 +0100
changeset 587807 5c010031f867afe239c52715012b602ecf3a0232
parent 576458 1ee09646e328f0d43feb567f209ab99f123776af
child 594123 b64887f75753972099026865acdbea2c89142738
permissions -rw-r--r--
Bug 1369471 - Allow running wpt directories by path on the command line, r=maja_zf We regressed running entire directories when wptrunner was switched to look up command parameters directly in the manifest rather than iterating over prefix matches. For files that's OK, but for directories the old behaviour is required. MozReview-Commit-ID: HVL7rL1YuZx

"""Manifest structure used to store paths that should be included in a test run.

The manifest is represented by a tree of IncludeManifest objects, the root
representing the file and each subnode representing a subdirectory that should
be included or excluded.
"""
import glob
import os
import urlparse

from wptmanifest.node import DataNode
from wptmanifest.backends import conditional
from wptmanifest.backends.conditional import ManifestItem


class IncludeManifest(ManifestItem):
    def __init__(self, node):
        """Node in a tree structure representing the paths
        that should be included or excluded from the test run.

        :param node: AST Node corresponding to this Node.
        """
        ManifestItem.__init__(self, node)
        self.child_map = {}

    @classmethod
    def create(cls):
        """Create an empty IncludeManifest tree"""
        node = DataNode(None)
        return cls(node)

    def append(self, child):
        ManifestItem.append(self, child)
        self.child_map[child.name] = child
        assert len(self.child_map) == len(self.children)

    def include(self, test):
        """Return a boolean indicating whether a particular test should be
        included in a test run, based on the IncludeManifest tree rooted on
        this object.

        :param test: The test object"""
        path_components = self._get_components(test.url)
        return self._include(test, path_components)

    def _include(self, test, path_components):
        if path_components:
            next_path_part = path_components.pop()
            if next_path_part in self.child_map:
                return self.child_map[next_path_part]._include(test, path_components)

        node = self
        while node:
            try:
                skip_value = self.get("skip", {"test_type": test.item_type}).lower()
                assert skip_value in ("true", "false")
                return skip_value != "true"
            except KeyError:
                if node.parent is not None:
                    node = node.parent
                else:
                    # Include by default
                    return True

    def _get_components(self, url):
        rv = []
        url_parts = urlparse.urlsplit(url)
        variant = ""
        if url_parts.query:
            variant += "?" + url_parts.query
        if url_parts.fragment:
            variant += "#" + url_parts.fragment
        if variant:
            rv.append(variant)
        rv.extend([item for item in reversed(url_parts.path.split("/")) if item])
        return rv

    def _add_rule(self, test_manifests, url, direction):
        maybe_path = os.path.join(os.path.abspath(os.curdir), url)
        rest, last = os.path.split(maybe_path)
        fragment = query = None
        if "#" in last:
            last, fragment = last.rsplit("#", 1)
        if "?" in last:
            last, query = last.rsplit("?", 1)

        maybe_path = os.path.join(rest, last)
        paths = glob.glob(maybe_path)

        if paths:
            urls = []
            for path in paths:
                for manifest, data in test_manifests.iteritems():
                    found = False
                    rel_path = os.path.relpath(path, data["tests_path"])
                    iterator = manifest.iterpath if os.path.isfile(path) else manifest.iterdir
                    for test in iterator(rel_path):
                        if not hasattr(test, "url"):
                            continue
                        url = test.url
                        if query or fragment:
                            parsed = urlparse.urlparse(url)
                            if ((query and query != parsed.query) or
                                (fragment and fragment != parsed.fragment)):
                                continue
                        urls.append(url)
                        found = True
                    if found:
                        break
        else:
            urls = [url]

        assert direction in ("include", "exclude")

        for url in urls:
            components = self._get_components(url)

            node = self
            while components:
                component = components.pop()
                if component not in node.child_map:
                    new_node = IncludeManifest(DataNode(component))
                    node.append(new_node)
                    new_node.set("skip", node.get("skip", {}))

                node = node.child_map[component]

            skip = False if direction == "include" else True
            node.set("skip", str(skip))

    def add_include(self, test_manifests, url_prefix):
        """Add a rule indicating that tests under a url path
        should be included in test runs

        :param url_prefix: The url prefix to include
        """
        return self._add_rule(test_manifests, url_prefix, "include")

    def add_exclude(self, test_manifests, url_prefix):
        """Add a rule indicating that tests under a url path
        should be excluded from test runs

        :param url_prefix: The url prefix to exclude
        """
        return self._add_rule(test_manifests, url_prefix, "exclude")


def get_manifest(manifest_path):
    with open(manifest_path) as f:
        return conditional.compile(f, data_cls_getter=lambda x, y: IncludeManifest)