1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 from collections
import namedtuple
11 from wptrunner
.wptmanifest
.serializer
import serialize
12 from wptrunner
.wptmanifest
.backends
import base
13 from wptrunner
.wptmanifest
.node
import KeyValueNode
15 here
= os
.path
.dirname(__file__
)
16 logger
= logging
.getLogger(__name__
)
19 class Compiler(base
.Compiler
):
20 def visit_KeyValueNode(self
, node
):
23 for child
in node
.children
:
24 values
.append(self
.visit(child
))
26 self
.output_node
.set(key_name
, values
)
28 def visit_ConditionalNode(self
, node
):
29 assert len(node
.children
) == 2
30 # For conditional nodes, just return the subtree
31 return serialize(node
.children
[0]), self
.visit(node
.children
[1])
33 def visit_UnaryExpressionNode(self
, node
):
34 raise NotImplementedError
36 def visit_BinaryExpressionNode(self
, node
):
37 raise NotImplementedError
39 def visit_UnaryOperatorNode(self
, node
):
40 raise NotImplementedError
42 def visit_BinaryOperatorNode(self
, node
):
43 raise NotImplementedError
46 class ExpectedManifest(base
.ManifestItem
):
47 def __init__(self
, node
):
48 """Object representing all the tests in a particular manifest"""
49 base
.ManifestItem
.__init
__(self
, node
)
52 def append(self
, child
):
53 """Add a test to the manifest"""
54 base
.ManifestItem
.append(self
, child
)
55 self
.child_map
[child
.name
] = child
57 def insert(self
, child
):
59 self
.node
.append(child
.node
)
61 def delete(self
, child
):
62 del self
.child_map
[child
.name
]
67 class TestManifestItem(ExpectedManifest
):
68 def set_expected(self
, other_manifest
):
69 for item
in self
.node
.children
:
70 if isinstance(item
, KeyValueNode
) and item
.data
== "expected":
71 assert "expected" in self
._data
73 del self
._data
["expected"]
76 for item
in other_manifest
.node
.children
:
77 if isinstance(item
, KeyValueNode
) and item
.data
== "expected":
78 assert "expected" in other_manifest
._data
80 self
.node
.children
.insert(0, item
)
81 self
._data
["expected"] = other_manifest
._data
.pop("expected")
85 def data_cls_getter(output_node
, visited_node
):
86 # visited_node is intentionally unused
87 if output_node
is None:
88 return ExpectedManifest
89 if isinstance(output_node
, ExpectedManifest
):
90 return TestManifestItem
94 def compile(stream
, data_cls_getter
=None, **kwargs
):
95 return base
.compile(Compiler
, stream
, data_cls_getter
=data_cls_getter
, **kwargs
)
98 def get_manifest(manifest_path
):
99 """Get the ExpectedManifest for a particular manifest path"""
101 with
open(manifest_path
, "rb") as f
:
103 return compile(f
, data_cls_getter
=data_cls_getter
)
106 sys
.stderr
.write("Error parsing:\n%s" % f
.read().decode("utf8"))
112 def indent(str_data
, indent
=2):
114 for line
in str_data
.splitlines():
115 rv
.append("%s%s" % (" " * indent
, line
))
119 class Differences(object):
125 def __nonzero__(self
):
126 return bool(self
.added
or self
.deleted
or self
.modified
)
130 for item
in self
.modified
:
131 if isinstance(item
, TestModified
):
133 " %s\n %s\n%s" % (item
[0], item
[1], indent(str(item
[2]), 4))
136 assert isinstance(item
, ExpectedModified
)
137 modified
.append(" %s\n %s %s" % item
)
138 return "Added:\n%s\nDeleted:\n%s\nModified:\n%s\n" % (
139 "\n".join(" %s:\n %s" % item
for item
in self
.added
),
140 "\n".join(" %s" % item
for item
in self
.deleted
),
145 TestModified
= namedtuple("TestModified", ["test", "test_manifest", "differences"])
148 ExpectedModified
= namedtuple(
149 "ExpectedModified", ["test", "ancestor_manifest", "new_manifest"]
153 def compare_test(test
, ancestor_manifest
, new_manifest
):
154 changes
= Differences()
156 compare_expected(changes
, None, ancestor_manifest
, new_manifest
)
158 for subtest
, ancestor_subtest_manifest
in ancestor_manifest
.child_map
.items():
162 ancestor_subtest_manifest
,
163 new_manifest
.child_map
.get(subtest
),
166 for subtest
, subtest_manifest
in new_manifest
.child_map
.items():
167 if subtest
not in ancestor_manifest
.child_map
:
168 changes
.added
.append((subtest
, subtest_manifest
))
173 def compare_expected(changes
, subtest
, ancestor_manifest
, new_manifest
):
175 ancestor_manifest
and ancestor_manifest
.has_key("expected") # noqa W601
177 new_manifest
and new_manifest
.has_key("expected") # noqa W601
179 changes
.modified
.append(
180 ExpectedModified(subtest
, ancestor_manifest
, new_manifest
)
184 and ancestor_manifest
.has_key("expected") # noqa W601
185 and not (new_manifest
and new_manifest
.has_key("expected")) # noqa W601
187 changes
.deleted
.append(subtest
)
190 and ancestor_manifest
.has_key("expected") # noqa W601
192 and new_manifest
.has_key("expected") # noqa W601
194 old_expected
= ancestor_manifest
.get("expected")
195 new_expected
= new_manifest
.get("expected")
196 if expected_values_changed(old_expected
, new_expected
):
197 changes
.modified
.append(
198 ExpectedModified(subtest
, ancestor_manifest
, new_manifest
)
202 def expected_values_changed(old_expected
, new_expected
):
203 if len(old_expected
) != len(new_expected
):
208 for dest
, cond_lines
in [(old_dict
, old_expected
), (new_dict
, new_expected
)]:
209 for cond_line
in cond_lines
:
210 if isinstance(cond_line
, tuple):
211 condition
, value
= cond_line
215 dest
[condition
] = value
217 return new_dict
!= old_dict
220 def record_changes(ancestor_manifest
, new_manifest
):
221 changes
= Differences()
223 for test
, test_manifest
in new_manifest
.child_map
.items():
224 if test
not in ancestor_manifest
.child_map
:
225 changes
.added
.append((test
, test_manifest
))
227 ancestor_test_manifest
= ancestor_manifest
.child_map
[test
]
228 test_differences
= compare_test(test
, ancestor_test_manifest
, test_manifest
)
230 changes
.modified
.append(
231 TestModified(test
, test_manifest
, test_differences
)
234 for test
, test_manifest
in ancestor_manifest
.child_map
.items():
235 if test
not in new_manifest
.child_map
:
236 changes
.deleted
.append(test
)
241 def apply_changes(current_manifest
, changes
):
242 for test
, test_manifest
in changes
.added
:
243 if test
in current_manifest
.child_map
:
244 current_manifest
.delete(current_manifest
.child_map
[test
])
245 current_manifest
.insert(test_manifest
)
247 for test
in changes
.deleted
:
248 if test
in current_manifest
.child_map
:
249 current_manifest
.delete(current_manifest
.child_map
[test
])
251 for item
in changes
.modified
:
252 if isinstance(item
, TestModified
):
253 test
, new_manifest
, test_changes
= item
254 if test
in current_manifest
.child_map
:
255 apply_changes(current_manifest
.child_map
[test
], test_changes
)
257 current_manifest
.insert(new_manifest
)
259 assert isinstance(item
, ExpectedModified
)
260 subtest
, ancestor_manifest
, new_manifest
= item
262 current_manifest
.set_expected(new_manifest
)
263 elif subtest
in current_manifest
.child_map
:
264 current_manifest
.child_map
[subtest
].set_expected(new_manifest
)
266 current_manifest
.insert(new_manifest
)
270 parser
= argparse
.ArgumentParser()
271 parser
.add_argument("ancestor")
272 parser
.add_argument("current")
273 parser
.add_argument("new")
274 parser
.add_argument("dest", nargs
="?")
278 def get_parser_mergetool():
279 parser
= argparse
.ArgumentParser()
280 parser
.add_argument("--no-overwrite", dest
="overwrite", action
="store_false")
284 def make_changes(ancestor_manifest
, current_manifest
, new_manifest
):
285 changes
= record_changes(ancestor_manifest
, new_manifest
)
286 apply_changes(current_manifest
, changes
)
288 return serialize(current_manifest
.node
)
291 def run(ancestor
, current
, new
, dest
):
292 ancestor_manifest
= get_manifest(ancestor
)
293 current_manifest
= get_manifest(current
)
294 new_manifest
= get_manifest(new
)
296 updated_current_str
= make_changes(
297 ancestor_manifest
, current_manifest
, new_manifest
301 with
open(dest
, "wb") as f
:
302 f
.write(updated_current_str
.encode("utf8"))
304 print(updated_current_str
)