tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

metamerge.py (9858B)


      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/.
      4 
      5 import argparse
      6 import logging
      7 import os
      8 import sys
      9 from collections import namedtuple
     10 
     11 from wptrunner.wptmanifest.backends import base
     12 from wptrunner.wptmanifest.node import KeyValueNode
     13 from wptrunner.wptmanifest.serializer import serialize
     14 
     15 here = os.path.dirname(__file__)
     16 logger = logging.getLogger(__name__)
     17 
     18 
     19 class Compiler(base.Compiler):
     20    def visit_KeyValueNode(self, node):
     21        key_name = node.data
     22        values = []
     23        for child in node.children:
     24            values.append(self.visit(child))
     25 
     26        self.output_node.set(key_name, values)
     27 
     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])
     32 
     33    def visit_UnaryExpressionNode(self, node):
     34        raise NotImplementedError
     35 
     36    def visit_BinaryExpressionNode(self, node):
     37        raise NotImplementedError
     38 
     39    def visit_UnaryOperatorNode(self, node):
     40        raise NotImplementedError
     41 
     42    def visit_BinaryOperatorNode(self, node):
     43        raise NotImplementedError
     44 
     45 
     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)
     50        self.child_map = {}
     51 
     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
     56 
     57    def insert(self, child):
     58        self.append(child)
     59        self.node.append(child.node)
     60 
     61    def delete(self, child):
     62        del self.child_map[child.name]
     63        child.node.remove()
     64        child.remove()
     65 
     66 
     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
     72                item.remove()
     73                del self._data["expected"]
     74                break
     75 
     76        for item in other_manifest.node.children:
     77            if isinstance(item, KeyValueNode) and item.data == "expected":
     78                assert "expected" in other_manifest._data
     79                item.remove()
     80                self.node.children.insert(0, item)
     81                self._data["expected"] = other_manifest._data.pop("expected")
     82                break
     83 
     84 
     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
     91    raise ValueError
     92 
     93 
     94 def compile(stream, data_cls_getter=None, **kwargs):
     95    return base.compile(Compiler, stream, data_cls_getter=data_cls_getter, **kwargs)
     96 
     97 
     98 def get_manifest(manifest_path):
     99    """Get the ExpectedManifest for a particular manifest path"""
    100    try:
    101        with open(manifest_path, "rb") as f:
    102            try:
    103                return compile(f, data_cls_getter=data_cls_getter)
    104            except Exception:
    105                f.seek(0)
    106                sys.stderr.write("Error parsing:\n%s" % f.read().decode("utf8"))
    107                raise
    108    except OSError:
    109        return None
    110 
    111 
    112 def indent(str_data, indent=2):
    113    rv = []
    114    for line in str_data.splitlines():
    115        rv.append("%s%s" % (" " * indent, line))
    116    return "\n".join(rv)
    117 
    118 
    119 class Differences:
    120    def __init__(self):
    121        self.added = []
    122        self.deleted = []
    123        self.modified = []
    124 
    125    def __nonzero__(self):
    126        return bool(self.added or self.deleted or self.modified)
    127 
    128    def __str__(self):
    129        modified = []
    130        for item in self.modified:
    131            if isinstance(item, TestModified):
    132                modified.append(
    133                    "  %s\n    %s\n%s" % (item[0], item[1], indent(str(item[2]), 4))
    134                )
    135            else:
    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),
    141            "\n".join(modified),
    142        )
    143 
    144 
    145 TestModified = namedtuple("TestModified", ["test", "test_manifest", "differences"])
    146 
    147 
    148 ExpectedModified = namedtuple(
    149    "ExpectedModified", ["test", "ancestor_manifest", "new_manifest"]
    150 )
    151 
    152 
    153 def compare_test(test, ancestor_manifest, new_manifest):
    154    changes = Differences()
    155 
    156    compare_expected(changes, None, ancestor_manifest, new_manifest)
    157 
    158    for subtest, ancestor_subtest_manifest in ancestor_manifest.child_map.items():
    159        compare_expected(
    160            changes,
    161            subtest,
    162            ancestor_subtest_manifest,
    163            new_manifest.child_map.get(subtest),
    164        )
    165 
    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))
    169 
    170    return changes
    171 
    172 
    173 def compare_expected(changes, subtest, ancestor_manifest, new_manifest):
    174    if not (
    175        ancestor_manifest and ancestor_manifest.has_key("expected")  # noqa W601
    176    ) and (
    177        new_manifest and new_manifest.has_key("expected")  # noqa W601
    178    ):
    179        changes.modified.append(
    180            ExpectedModified(subtest, ancestor_manifest, new_manifest)
    181        )
    182    elif (
    183        ancestor_manifest
    184        and ancestor_manifest.has_key("expected")  # noqa W601
    185        and not (new_manifest and new_manifest.has_key("expected"))  # noqa W601
    186    ):
    187        changes.deleted.append(subtest)
    188    elif (
    189        ancestor_manifest
    190        and ancestor_manifest.has_key("expected")  # noqa W601
    191        and new_manifest
    192        and new_manifest.has_key("expected")  # noqa W601
    193    ):
    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)
    199            )
    200 
    201 
    202 def expected_values_changed(old_expected, new_expected):
    203    if len(old_expected) != len(new_expected):
    204        return True
    205 
    206    old_dict = {}
    207    new_dict = {}
    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
    212            else:
    213                condition = None
    214                value = cond_line
    215            dest[condition] = value
    216 
    217    return new_dict != old_dict
    218 
    219 
    220 def record_changes(ancestor_manifest, new_manifest):
    221    changes = Differences()
    222 
    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))
    226        else:
    227            ancestor_test_manifest = ancestor_manifest.child_map[test]
    228            test_differences = compare_test(test, ancestor_test_manifest, test_manifest)
    229            if test_differences:
    230                changes.modified.append(
    231                    TestModified(test, test_manifest, test_differences)
    232                )
    233 
    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)
    237 
    238    return changes
    239 
    240 
    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)
    246 
    247    for test in changes.deleted:
    248        if test in current_manifest.child_map:
    249            current_manifest.delete(current_manifest.child_map[test])
    250 
    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)
    256            else:
    257                current_manifest.insert(new_manifest)
    258        else:
    259            assert isinstance(item, ExpectedModified)
    260            subtest, ancestor_manifest, new_manifest = item
    261            if not subtest:
    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)
    265            else:
    266                current_manifest.insert(new_manifest)
    267 
    268 
    269 def get_parser():
    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="?")
    275    return parser
    276 
    277 
    278 def get_parser_mergetool():
    279    parser = argparse.ArgumentParser()
    280    parser.add_argument("--no-overwrite", dest="overwrite", action="store_false")
    281    return parser
    282 
    283 
    284 def make_changes(ancestor_manifest, current_manifest, new_manifest):
    285    changes = record_changes(ancestor_manifest, new_manifest)
    286    apply_changes(current_manifest, changes)
    287 
    288    return serialize(current_manifest.node)
    289 
    290 
    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)
    295 
    296    updated_current_str = make_changes(
    297        ancestor_manifest, current_manifest, new_manifest
    298    )
    299 
    300    if dest != "-":
    301        with open(dest, "wb") as f:
    302            f.write(updated_current_str.encode("utf8"))
    303    else:
    304        print(updated_current_str)