summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/harness/wptrunner/manifestinclude.py
blob: d9b4ee455a6d0613f1a7c7333db3a6039939a1b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# 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/.

"""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)
        variant = ""
        if "#" in last:
            last, fragment = last.rsplit("#", 1)
            variant += "#" + fragment
        if "?" in last:
            last, query = last.rsplit("?", 1)
            variant += "?" + query

        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():
                    rel_path = os.path.relpath(path, data["tests_path"])
                    if ".." not in rel_path.split(os.sep):
                        urls.append(data["url_base"] + rel_path.replace(os.path.sep, "/") + variant)
                        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)