summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/harness/wptrunner/manifestexpected.py
blob: c0e22a843fdfeb4e91a638a31f098a74aed24362 (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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# 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 bool_prop(name, node):
    """Boolean property"""
    try:
        return node.get(name)
    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 bool_prop("disabled", self)

    @property
    def restart_after(self):
        return bool_prop("restart-after", self)

    @property
    def tags(self):
        return tags(self)

    @property
    def prefs(self):
        return prefs(self)


class DirectoryManifest(ManifestItem):
    @property
    def disabled(self):
        return bool_prop("disabled", self)

    @property
    def restart_after(self):
        return bool_prop("restart-after", 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 bool_prop("disabled", self)

    @property
    def restart_after(self):
        return bool_prop("restart-after", 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