Ejemplo n.º 1
0
def test_patch_str():
    # Test +, single item insertion
    assert patch("42", [op_patch(0, [op_add(0, "3"), op_remove(1)])]) == "34"

    # Test -, single item deletion
    assert patch("3", [op_patch(0, [op_remove(0)])]) == ""
    assert patch("42", [op_patch(0, [op_remove(0)])]) == "2"
    assert patch("425", [op_patch(0, [op_remove(0)])]) == "25"
    assert patch("425", [op_patch(0, [op_remove(1)])]) == "45"
    assert patch("425", [op_patch(0, [op_remove(2)])]) == "42"

    # Test :, single item replace
    assert patch("52", [op_patch(0, [op_replace(0, "4")])]) == "42"
    assert patch("41", [op_patch(0, [op_replace(1, "2")])]) == "42"
    assert patch("42", [op_patch(0, [op_replace(0, "3"), op_replace(1, "5")])]) == "35"
    assert patch("hello", [op_patch(0, [op_replace(0, "H")])]) == "Hello"
    # Replace by delete-then-insert
    assert patch("world", [op_patch(0, [op_remove(0), op_add(0, "W")])]) == "World"

    # Test !, item patch (doesn't make sense for str)
    pass

    # Test ++, sequence insertion
    assert patch("", [op_patch(0, [op_addrange(0, "34"), op_add(0, "5"), op_addrange(0, "67")])]) == "34567"

    # Test --, sequence deletion
    assert patch("abcd", [op_patch(0, [op_removerange(0, 2)])]) == "cd"
    assert patch("abcd", [op_patch(0, [op_removerange(1, 2)])]) == "ad"
    assert patch("abcd", [op_patch(0, [op_removerange(2, 2)])]) == "ab"
Ejemplo n.º 2
0
def test_deep_merge_dicts_insert_conflicted():
    # local and remote adds the same entry plus an entry each in a new subdict
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1}, "n": {"s": 7, "l": 2}}
    r = {"p": {"b": 1}, "n": {"s": 7, "r": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == b
    assert len(decisions) == 1
    d = decisions[0]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_add("n", l["n"])]
    assert d.remote_diff == [op_add("n", r["n"])]
Ejemplo n.º 3
0
def test_deep_merge_dicts_insert_conflicted():
    # local and remote adds the same entry plus an entry each in a new subdict
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1}, "n": {"s": 7, "l": 2}}
    r = {"p": {"b": 1}, "n": {"s": 7, "r": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == b
    assert len(decisions) == 1
    d = decisions[0]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_add("n", l["n"])]
    assert d.remote_diff == [op_add("n", r["n"])]
Ejemplo n.º 4
0
def test_patch_list():
    # Test +, single item insertion
    assert patch([], [op_add(0, 3)]) == [3]
    assert patch([], [op_add(0, 3), op_add(0, 4)]) == [3, 4]
    assert patch([], [op_add(0, 3), op_add(0, 4), op_add(0, 5)]) == [3, 4, 5]

    # Test -, single item deletion
    assert patch([3], [op_remove(0)]) == []
    assert patch([5, 6, 7], [op_remove(0)]) == [6, 7]
    assert patch([5, 6, 7], [op_remove(1)]) == [5, 7]
    assert patch([5, 6, 7], [op_remove(2)]) == [5, 6]
    assert patch([5, 6, 7], [op_remove(0), op_remove(2)]) == [6]

    # Test :, single item replace
    pass

    # Test !, item patch
    assert patch(["hello", "world"], [
        op_patch(0, [op_patch(0, [op_replace(0, "H")])]),
        op_patch(1, [op_patch(0, [op_remove(0), op_add(0, "W")])])
    ]) == ["Hello", "World"]

    # Test ++, sequence insertion
    assert patch(
        [], [op_addrange(0, [3, 4]),
             op_add(0, 5),
             op_addrange(0, [6, 7])]) == [3, 4, 5, 6, 7]

    # Test --, sequence deletion
    assert patch([5, 6, 7, 8], [op_removerange(0, 2)]) == [7, 8]
    assert patch([5, 6, 7, 8], [op_removerange(1, 2)]) == [5, 8]
    assert patch([5, 6, 7, 8], [op_removerange(2, 2)]) == [5, 6]
Ejemplo n.º 5
0
def test_patch_list():
    # Test +, single item insertion
    assert patch([], [op_add(0, 3)]) == [3]
    assert patch([], [op_add(0, 3), op_add(0, 4)]) == [3, 4]
    assert patch([], [op_add(0, 3), op_add(0, 4), op_add(0, 5)]) == [3, 4, 5]

    # Test -, single item deletion
    assert patch([3], [op_remove(0)]) == []
    assert patch([5, 6, 7], [op_remove(0)]) == [6, 7]
    assert patch([5, 6, 7], [op_remove(1)]) == [5, 7]
    assert patch([5, 6, 7], [op_remove(2)]) == [5, 6]
    assert patch([5, 6, 7], [op_remove(0), op_remove(2)]) == [6]

    # Test :, single item replace
    pass

    # Test !, item patch
    assert patch(["hello", "world"], [op_patch(0, [op_patch(0, [op_replace(0, "H")])]),
                                      op_patch(1, [op_patch(0, [op_remove(0), op_add(0, "W")])])]) == ["Hello", "World"]

    # Test ++, sequence insertion
    assert patch([], [op_addrange(0, [3, 4]), op_add(0, 5), op_addrange(0, [6, 7])]) == [3, 4, 5, 6, 7]

    # Test --, sequence deletion
    assert patch([5, 6, 7, 8], [op_removerange(0, 2)]) == [7, 8]
    assert patch([5, 6, 7, 8], [op_removerange(1, 2)]) == [5, 8]
    assert patch([5, 6, 7, 8], [op_removerange(2, 2)]) == [5, 6]
Ejemplo n.º 6
0
def test_patch_dict():
    # Test +, single item insertion
    assert patch({}, [op_add("d", 4)]) == {"d": 4}
    assert patch({"a": 1}, [op_add("d", 4)]) == {"a": 1, "d": 4}

    #assert patch({"d": 1}, [op_add("d", 4)]) == {"d": 4} # currently triggers assert, raise exception or allow?

    # Test -, single item deletion
    assert patch({"a": 1}, [op_remove("a")]) == {}
    assert patch({"a": 1, "b": 2}, [op_remove("a")]) == {"b": 2}

    # Test :, single item replace
    assert patch({"a": 1, "b": 2}, [op_replace("a", 3)]) == {"a": 3, "b": 2}
    assert patch({"a": 1, "b": 2}, [op_replace("a", 3), op_replace("b", 5)]) == {"a": 3, "b": 5}

    # Test !, item patch
    subdiff = [op_patch(0, [op_patch(0, [op_replace(0, "H")])]), op_patch(1, [op_patch(0, [op_remove(0), op_add(0, "W")])])]
    assert patch({"a": ["hello", "world"], "b": 3}, [op_patch("a", subdiff)]) == {"a": ["Hello", "World"], "b": 3}
Ejemplo n.º 7
0
def test_patch_dict():
    # Test +, single item insertion
    assert patch({}, [op_add("d", 4)]) == {"d": 4}
    assert patch({"a": 1}, [op_add("d", 4)]) == {"a": 1, "d": 4}

    #assert patch({"d": 1}, [op_add("d", 4)]) == {"d": 4} # currently triggers assert, raise exception or allow?

    # Test -, single item deletion
    assert patch({"a": 1}, [op_remove("a")]) == {}
    assert patch({"a": 1, "b": 2}, [op_remove("a")]) == {"b": 2}

    # Test :, single item replace
    assert patch({"a": 1, "b": 2}, [op_replace("a", 3)]) == {"a": 3, "b": 2}
    assert patch({"a": 1, "b": 2}, [op_replace("a", 3), op_replace("b", 5)]) == {"a": 3, "b": 5}

    # Test !, item patch
    subdiff = [op_patch(0, [op_patch(0, [op_replace(0, "H")])]), op_patch(1, [op_patch(0, [op_remove(0), op_add(0, "W")])])]
    assert patch({"a": ["hello", "world"], "b": 3}, [op_patch("a", subdiff)]) == {"a": ["Hello", "World"], "b": 3}
Ejemplo n.º 8
0
def test_patch_str():
    # Test +, single item insertion
    assert patch("42", [op_patch(0, [op_add(0, "3"), op_remove(1)])]) == "34"

    # Test -, single item deletion
    assert patch("3", [op_patch(0, [op_remove(0)])]) == ""
    assert patch("42", [op_patch(0, [op_remove(0)])]) == "2"
    assert patch("425", [op_patch(0, [op_remove(0)])]) == "25"
    assert patch("425", [op_patch(0, [op_remove(1)])]) == "45"
    assert patch("425", [op_patch(0, [op_remove(2)])]) == "42"

    # Test :, single item replace
    assert patch("52", [op_patch(0, [op_replace(0, "4")])]) == "42"
    assert patch("41", [op_patch(0, [op_replace(1, "2")])]) == "42"
    assert patch("42", [op_patch(
        0, [op_replace(0, "3"), op_replace(1, "5")])]) == "35"
    assert patch("hello", [op_patch(0, [op_replace(0, "H")])]) == "Hello"
    # Replace by delete-then-insert
    assert patch("world",
                 [op_patch(0, [op_remove(0), op_add(0, "W")])]) == "World"

    # Test !, item patch (doesn't make sense for str)
    pass

    # Test ++, sequence insertion
    assert patch("", [
        op_patch(0,
                 [op_addrange(0, "34"),
                  op_add(0, "5"),
                  op_addrange(0, "67")])
    ]) == "34567"

    # Test --, sequence deletion
    assert patch("abcd", [op_patch(0, [op_removerange(0, 2)])]) == "cd"
    assert patch("abcd", [op_patch(0, [op_removerange(1, 2)])]) == "ad"
    assert patch("abcd", [op_patch(0, [op_removerange(2, 2)])]) == "ab"
Ejemplo n.º 9
0
def test_diff_and_patch():
    # Note: check_symmetric_diff_and_patch handles (a,b) and (b,a) for both
    # shallow and deep diffs, simplifying the number of cases to cover in here.

    # Empty
    mda = {}
    mdb = {}
    check_symmetric_diff_and_patch(mda, mdb)

    # One-sided content/empty
    mda = {"a": 1}
    mdb = {}
    check_symmetric_diff_and_patch(mda, mdb)

    # One-sided content/empty multilevel
    mda = {"a": 1, "b": {"ba": 21}}
    mdb = {}
    check_symmetric_diff_and_patch(mda, mdb)

    # One-sided content/empty multilevel
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {}
    check_symmetric_diff_and_patch(mda, mdb)

    # Partial delete
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31}}
    check_symmetric_diff_and_patch(mda, mdb)
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    check_symmetric_diff_and_patch(mda, mdb)
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"b": {"ba": 21}, "c": {"cb": 32}}
    check_symmetric_diff_and_patch(mda, mdb)

    # One-level modification
    mda = {"a": 1}
    mdb = {"a": 10}
    check_symmetric_diff_and_patch(mda, mdb)

    # Two-level modification
    mda = {"a": 1, "b": {"ba": 21}}
    mdb = {"a": 10, "b": {"ba": 210}}
    check_symmetric_diff_and_patch(mda, mdb)
    mda = {"a": 1, "b": {"ba": 21}}
    mdb = {"a": 1, "b": {"ba": 210}}
    check_symmetric_diff_and_patch(mda, mdb)

    # Multilevel modification
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"a": 10, "b": {"ba": 210}, "c": {"ca": 310, "cb": 320}}
    check_symmetric_diff_and_patch(mda, mdb)
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"a": 1, "b": {"ba": 210}, "c": {"ca": 310, "cb": 32}}
    check_symmetric_diff_and_patch(mda, mdb)

    # Multilevel mix of delete, add, modify
    mda = {
        "deleted": 1,
        "modparent": {
            "mod": 21
        },
        "mix": {
            "del": 31,
            "mod": 32,
            "unchanged": 123
        }
    }
    mdb = {
        "added": 7,
        "modparent": {
            "mod": 22
        },
        "mix": {
            "add": 42,
            "mod": 37,
            "unchanged": 123
        }
    }
    check_symmetric_diff_and_patch(mda, mdb)
    # A more explicit assert showing the diff format and testing that paths are sorted:
    assert diff(mda, mdb) == [
        op_add("added", 7),
        op_remove("deleted"),
        op_patch("mix", [
            op_add("add", 42),
            op_remove("del"),
            op_replace("mod", 37),
        ]),
        op_patch("modparent", [op_replace("mod", 22)]),
    ]
Ejemplo n.º 10
0
def test_merge_conflicting_nested_dicts():
    # Note: Tests in here were written by writing up the last version
    # and then copy-pasting and deleting pieces to simplify...
    # Not pretty for production code but the explicitness is convenient when the tests fail.

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}}
    l = {"a": {"x": 2}}
    r = {"a": {"x": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"a": {"x": 1}}
    assert len(decisions) == 1
    d = decisions[0]
    assert d.common_path == ("a",)
    assert d.local_diff == [op_replace("x", 2)]
    assert d.remote_diff == [op_replace("x", 3)]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {}}
    l = {"a": {"y": 4}}
    r = {"a": {"y": 5}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"a": {}}
    assert len(decisions) == 1
    d = decisions[0]
    assert d.common_path == ("a",)
    assert d.local_diff  == [op_add("y", 4),
                  ]
    assert d.remote_diff  == [op_add("y", 5),
                  ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}}
    l = {"a": {"x": 2, "y": 4}}
    r = {"a": {"x": 3, "y": 5}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"a": {"x": 1}}
    assert len(decisions) == 2
    assert all([d.conflict for d in decisions])
    assert all([d.common_path == ("a",) for d in decisions])
    assert decisions[0].local_diff == [op_replace("x", 2)]
    assert decisions[0].remote_diff == [op_replace("x", 3)]
    assert decisions[1].local_diff == [op_add("y", 4)]
    assert decisions[1].remote_diff == [op_add("y", 5)]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1},         "d": {"x": 4, "y": 5}}
    l = {"a": {"x": 2, "y": 4}, "d":         {"y": 6}}
    r = {"a": {"x": 3, "y": 5}, "d": {"x": 5},       }
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"a": {"x": 1}, "d": {"x": 4, "y": 5}}
    assert len(decisions) == 4
    assert all([d.conflict for d in decisions])
    assert decisions[0].common_path == ("d",)
    assert decisions[1].common_path == ("d",)
    assert decisions[2].common_path == ("a",)
    assert decisions[3].common_path == ("a",)

    assert decisions[0].local_diff == [op_remove("x")]
    assert decisions[0].remote_diff == [op_replace("x", 5)]

    assert decisions[1].local_diff == [op_replace("y", 6)]
    assert decisions[1].remote_diff == [op_remove("y")]

    assert decisions[2].local_diff == [op_replace("x", 2)]
    assert decisions[2].remote_diff == [op_replace("x", 3)]

    assert decisions[3].local_diff == [op_add("y", 4)]
    assert decisions[3].remote_diff == [op_add("y", 5)]


    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1},         "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    l = {"a": {"x": 2, "y": 4}, "d":         {"y": 6}, "m": {"x": 17}}
    r = {"a": {"x": 3, "y": 5}, "d": {"x": 5},         "m": {"x": 27}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {
        "a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    assert len(decisions) == 5
    assert all([d.conflict for d in decisions])
    assert decisions[0].common_path == ("m",)
    assert decisions[1].common_path == ("d",)
    assert decisions[2].common_path == ("d",)
    assert decisions[3].common_path == ("a",)
    assert decisions[4].common_path == ("a",)

    assert decisions[0].local_diff == [op_replace("x", 17)]
    assert decisions[0].remote_diff == [op_replace("x", 27)]

    assert decisions[1].local_diff == [op_remove("x")]
    assert decisions[1].remote_diff == [op_replace("x", 5)]

    assert decisions[2].local_diff == [op_replace("y", 6)]
    assert decisions[2].remote_diff == [op_remove("y")]

    assert decisions[3].local_diff == [op_replace("x", 2)]
    assert decisions[3].remote_diff == [op_replace("x", 3)]

    assert decisions[4].local_diff == [op_add("y", 4)]
    assert decisions[4].remote_diff == [op_add("y", 5)]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1},
         "d": {"x": 4, "y": 5},
         "m": {"x": 7}}
    l = {"a": {"x": 2, "y": 4},
         "d": {"y": 6},
         "m": {"x": 17},
         "n": {"q": 9}}
    r = {"a": {"x": 3, "y": 5},
         "d": {"x": 5},
         "m": {"x": 27},
         "n": {"q": 19}}
    decisions = decide_merge(b, l, r)
    # Note that "n":{} gets added to the merge result even though it's empty
    assert apply_decisions(b, decisions) == {
        "a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    assert len(decisions) == 6
    assert all([d.conflict for d in decisions])
    assert decisions[0].common_path == ("m",)
    assert decisions[1].common_path == ("d",)
    assert decisions[2].common_path == ("d",)
    assert decisions[3].common_path == ("a",)
    assert decisions[4].common_path == ("a",)
    assert decisions[5].common_path == ()

    assert decisions[0].local_diff == [op_replace("x", 17)]
    assert decisions[0].remote_diff == [op_replace("x", 27)]

    assert decisions[1].local_diff == [op_remove("x")]
    assert decisions[1].remote_diff == [op_replace("x", 5)]

    assert decisions[2].local_diff == [op_replace("y", 6)]
    assert decisions[2].remote_diff == [op_remove("y")]

    assert decisions[3].local_diff == [op_replace("x", 2)]
    assert decisions[3].remote_diff == [op_replace("x", 3)]

    assert decisions[4].local_diff == [op_add("y", 4)]
    assert decisions[4].remote_diff == [op_add("y", 5)]

    assert decisions[5].local_diff == [op_add("n", {"q": 9})]
    assert decisions[5].remote_diff == [op_add("n", {"q": 19})]
Ejemplo n.º 11
0
def test_merge_conflicting_nested_dicts():
    # Note: Tests in here were written by writing up the last version
    # and then copy-pasting and deleting pieces to simplify...
    # Not pretty for production code but the explicitness is convenient when the tests fail.

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}}
    l = {"a": {"x": 2}}
    r = {"a": {"x": 3}}
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {"x": 1}}
    assert lc == [
        op_patch("a", [op_replace("x", 2)]),
    ]
    assert rc == [
        op_patch("a", [op_replace("x", 3)]),
    ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {}}
    l = {"a": {"y": 4}}
    r = {"a": {"y": 5}}
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {}}
    assert lc == [
        op_patch("a", [op_add("y", 4)]),
    ]
    assert rc == [
        op_patch("a", [op_add("y", 5)]),
    ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}}
    l = {"a": {"x": 2, "y": 4}}
    r = {"a": {"x": 3, "y": 5}}
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {"x": 1}}
    assert lc == [
        op_patch("a", [op_replace("x", 2), op_add("y", 4)]),
    ]
    assert rc == [
        op_patch("a", [op_replace("x", 3), op_add("y", 5)]),
    ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}, "d": {"x": 4, "y": 5}}
    l = {"a": {"x": 2, "y": 4}, "d": {"y": 6}}
    r = {
        "a": {
            "x": 3,
            "y": 5
        },
        "d": {
            "x": 5
        },
    }
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {"x": 1}, "d": {"x": 4, "y": 5}}
    assert lc == [
        op_patch("a", [op_replace("x", 2), op_add("y", 4)]),
        op_patch("d", [op_remove("x"), op_replace("y", 6)]),
    ]
    assert rc == [
        op_patch("a", [op_replace("x", 3), op_add("y", 5)]),
        op_patch("d", [op_replace("x", 5), op_remove("y")]),
    ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    l = {"a": {"x": 2, "y": 4}, "d": {"y": 6}, "m": {"x": 17}}
    r = {"a": {"x": 3, "y": 5}, "d": {"x": 5}, "m": {"x": 27}}
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    assert lc == [
        op_patch("a", [op_replace("x", 2), op_add("y", 4)]),
        op_patch("d", [op_remove("x"), op_replace("y", 6)]),
        op_patch("m", [op_replace("x", 17)]),
    ]
    assert rc == [
        op_patch("a", [op_replace("x", 3), op_add("y", 5)]),
        op_patch("d", [op_replace("x", 5), op_remove("y")]),
        op_patch("m", [op_replace("x", 27)]),
    ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    l = {"a": {"x": 2, "y": 4}, "d": {"y": 6}, "m": {"x": 17}, "n": {"q": 9}}
    r = {"a": {"x": 3, "y": 5}, "d": {"x": 5}, "m": {"x": 27}, "n": {"q": 19}}
    m, lc, rc = merge(b, l, r)
    # Note that "n":{} gets added to the merge result even though it's empty
    assert m == {"a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}, "n": {}}
    assert lc == [
        op_patch("a", [op_replace("x", 2), op_add("y", 4)]),
        op_patch("d", [op_remove("x"), op_replace("y", 6)]),
        op_patch("m", [op_replace("x", 17)]),
        op_patch("n", [op_add("q", 9)])
    ]
    assert rc == [
        op_patch("a", [op_replace("x", 3), op_add("y", 5)]),
        op_patch("d", [op_replace("x", 5), op_remove("y")]),
        op_patch("m", [op_replace("x", 27)]),
        op_patch("n", [op_add("q", 19)])
    ]
Ejemplo n.º 12
0
def test_merge_conflicting_nested_dicts():
    # Note: Tests in here were written by writing up the last version
    # and then copy-pasting and deleting pieces to simplify...
    # Not pretty for production code but the explicitness is convenient when the tests fail.

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}}
    l = {"a": {"x": 2}}
    r = {"a": {"x": 3}}
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {"x": 1}}
    assert lc == [op_patch("a", [op_replace("x", 2)]),
                  ]
    assert rc == [op_patch("a", [op_replace("x", 3)]),
                  ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {}}
    l = {"a": {"y": 4}}
    r = {"a": {"y": 5}}
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {}}
    assert lc == [op_patch("a", [op_add("y", 4)]),
                  ]
    assert rc == [op_patch("a", [op_add("y", 5)]),
                  ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}}
    l = {"a": {"x": 2, "y": 4}}
    r = {"a": {"x": 3, "y": 5}}
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {"x": 1}}
    assert lc == [op_patch("a", [op_replace("x", 2), op_add("y", 4)]),
                  ]
    assert rc == [op_patch("a", [op_replace("x", 3), op_add("y", 5)]),
                  ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1},         "d": {"x": 4, "y": 5}}
    l = {"a": {"x": 2, "y": 4}, "d":         {"y": 6}}
    r = {"a": {"x": 3, "y": 5}, "d": {"x": 5},       }
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {"x": 1}, "d": {"x": 4, "y": 5}}
    assert lc == [op_patch("a", [op_replace("x", 2), op_add("y", 4)]),
                  op_patch("d", [op_remove("x"), op_replace("y", 6)]),
                  ]
    assert rc == [op_patch("a", [op_replace("x", 3), op_add("y", 5)]),
                  op_patch("d", [op_replace("x", 5), op_remove("y")]),
                  ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1},         "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    l = {"a": {"x": 2, "y": 4}, "d":         {"y": 6}, "m": {"x": 17}}
    r = {"a": {"x": 3, "y": 5}, "d": {"x": 5},         "m": {"x": 27}}
    m, lc, rc = merge(b, l, r)
    assert m == {"a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    assert lc == [op_patch("a", [op_replace("x", 2), op_add("y", 4)]),
                  op_patch("d", [op_remove("x"), op_replace("y", 6)]),
                  op_patch("m", [op_replace("x", 17)]),
                  ]
    assert rc == [op_patch("a", [op_replace("x", 3), op_add("y", 5)]),
                  op_patch("d", [op_replace("x", 5), op_remove("y")]),
                  op_patch("m", [op_replace("x", 27)]),
                  ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1},         "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    l = {"a": {"x": 2, "y": 4}, "d":         {"y": 6}, "m": {"x": 17}, "n": {"q": 9}}
    r = {"a": {"x": 3, "y": 5}, "d": {"x": 5},         "m": {"x": 27}, "n": {"q": 19}}
    m, lc, rc = merge(b, l, r)
    # Note that "n":{} gets added to the merge result even though it's empty
    assert m == {"a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}, "n": {}}
    assert lc == [op_patch("a", [op_replace("x", 2), op_add("y", 4)]),
                  op_patch("d", [op_remove("x"), op_replace("y", 6)]),
                  op_patch("m", [op_replace("x", 17)]),
                  op_patch("n", [op_add("q", 9)])
                  ]
    assert rc == [op_patch("a", [op_replace("x", 3), op_add("y", 5)]),
                  op_patch("d", [op_replace("x", 5), op_remove("y")]),
                  op_patch("m", [op_replace("x", 27)]),
                  op_patch("n", [op_add("q", 19)])
                  ]
Ejemplo n.º 13
0
def test_diff_and_patch():
    # Note: check_symmetric_diff_and_patch handles (a,b) and (b,a) for both
    # shallow and deep diffs, simplifying the number of cases to cover in here.

    # Empty
    mda = {}
    mdb = {}
    check_symmetric_diff_and_patch(mda, mdb)

    # One-sided content/empty
    mda = {"a": 1}
    mdb = {}
    check_symmetric_diff_and_patch(mda, mdb)

    # One-sided content/empty multilevel
    mda = {"a": 1, "b": {"ba": 21}}
    mdb = {}
    check_symmetric_diff_and_patch(mda, mdb)

    # One-sided content/empty multilevel
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {}
    check_symmetric_diff_and_patch(mda, mdb)

    # Partial delete
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31}}
    check_symmetric_diff_and_patch(mda, mdb)
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    check_symmetric_diff_and_patch(mda, mdb)
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"b": {"ba": 21}, "c": {"cb": 32}}
    check_symmetric_diff_and_patch(mda, mdb)

    # One-level modification
    mda = {"a": 1}
    mdb = {"a": 10}
    check_symmetric_diff_and_patch(mda, mdb)

    # Two-level modification
    mda = {"a": 1, "b": {"ba": 21}}
    mdb = {"a": 10, "b": {"ba": 210}}
    check_symmetric_diff_and_patch(mda, mdb)
    mda = {"a": 1, "b": {"ba": 21}}
    mdb = {"a": 1, "b": {"ba": 210}}
    check_symmetric_diff_and_patch(mda, mdb)

    # Multilevel modification
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"a": 10, "b": {"ba": 210}, "c": {"ca": 310, "cb": 320}}
    check_symmetric_diff_and_patch(mda, mdb)
    mda = {"a": 1, "b": {"ba": 21}, "c": {"ca": 31, "cb": 32}}
    mdb = {"a": 1, "b": {"ba": 210}, "c": {"ca": 310, "cb": 32}}
    check_symmetric_diff_and_patch(mda, mdb)

    # Multilevel mix of delete, add, modify
    mda = {"deleted": 1, "modparent": {"mod": 21}, "mix": {"del": 31, "mod": 32, "unchanged": 123}}
    mdb = {"added": 7,   "modparent": {"mod": 22}, "mix": {"add": 42, "mod": 37, "unchanged": 123}}
    check_symmetric_diff_and_patch(mda, mdb)
    # A more explicit assert showing the diff format and testing that paths are sorted:
    assert diff(mda, mdb) == [
        op_add("added", 7),
        op_remove("deleted"),
        op_patch("mix", [
            op_add("add", 42),
            op_remove("del"),
            op_replace("mod", 37),
            ]),
        op_patch("modparent", [
            op_replace("mod", 22)
            ]),
        ]
Ejemplo n.º 14
0
def test_merge_conflicting_nested_dicts():
    # Note: Tests in here were written by writing up the last version
    # and then copy-pasting and deleting pieces to simplify...
    # Not pretty for production code but the explicitness is convenient when the tests fail.

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}}
    l = {"a": {"x": 2}}
    r = {"a": {"x": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"a": {"x": 1}}
    assert len(decisions) == 1
    d = decisions[0]
    assert d.common_path == ("a",)
    assert d.local_diff == [op_replace("x", 2)]
    assert d.remote_diff == [op_replace("x", 3)]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {}}
    l = {"a": {"y": 4}}
    r = {"a": {"y": 5}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"a": {}}
    assert len(decisions) == 1
    d = decisions[0]
    assert d.common_path == ("a",)
    assert d.local_diff  == [op_add("y", 4),
                  ]
    assert d.remote_diff  == [op_add("y", 5),
                  ]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1}}
    l = {"a": {"x": 2, "y": 4}}
    r = {"a": {"x": 3, "y": 5}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"a": {"x": 1}}
    assert len(decisions) == 2
    assert all([d.conflict for d in decisions])
    assert all([d.common_path == ("a",) for d in decisions])
    assert decisions[0].local_diff == [op_replace("x", 2)]
    assert decisions[0].remote_diff == [op_replace("x", 3)]
    assert decisions[1].local_diff == [op_add("y", 4)]
    assert decisions[1].remote_diff == [op_add("y", 5)]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1},         "d": {"x": 4, "y": 5}}
    l = {"a": {"x": 2, "y": 4}, "d":         {"y": 6}}
    r = {"a": {"x": 3, "y": 5}, "d": {"x": 5},       }
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"a": {"x": 1}, "d": {"x": 4, "y": 5}}
    assert len(decisions) == 4
    assert all([d.conflict for d in decisions])
    assert decisions[0].common_path == ("d",)
    assert decisions[1].common_path == ("d",)
    assert decisions[2].common_path == ("a",)
    assert decisions[3].common_path == ("a",)

    assert decisions[0].local_diff == [op_remove("x")]
    assert decisions[0].remote_diff == [op_replace("x", 5)]

    assert decisions[1].local_diff == [op_replace("y", 6)]
    assert decisions[1].remote_diff == [op_remove("y")]

    assert decisions[2].local_diff == [op_replace("x", 2)]
    assert decisions[2].remote_diff == [op_replace("x", 3)]

    assert decisions[3].local_diff == [op_add("y", 4)]
    assert decisions[3].remote_diff == [op_add("y", 5)]


    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1},         "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    l = {"a": {"x": 2, "y": 4}, "d":         {"y": 6}, "m": {"x": 17}}
    r = {"a": {"x": 3, "y": 5}, "d": {"x": 5},         "m": {"x": 27}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {
        "a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    assert len(decisions) == 5
    assert all([d.conflict for d in decisions])
    assert decisions[0].common_path == ("m",)
    assert decisions[1].common_path == ("d",)
    assert decisions[2].common_path == ("d",)
    assert decisions[3].common_path == ("a",)
    assert decisions[4].common_path == ("a",)

    assert decisions[0].local_diff == [op_replace("x", 17)]
    assert decisions[0].remote_diff == [op_replace("x", 27)]

    assert decisions[1].local_diff == [op_remove("x")]
    assert decisions[1].remote_diff == [op_replace("x", 5)]

    assert decisions[2].local_diff == [op_replace("y", 6)]
    assert decisions[2].remote_diff == [op_remove("y")]

    assert decisions[3].local_diff == [op_replace("x", 2)]
    assert decisions[3].remote_diff == [op_replace("x", 3)]

    assert decisions[4].local_diff == [op_add("y", 4)]
    assert decisions[4].remote_diff == [op_add("y", 5)]

    # local and remote each adds, deletes, and modifies entries inside nested structure with everything conflicting
    b = {"a": {"x": 1},
         "d": {"x": 4, "y": 5},
         "m": {"x": 7}}
    l = {"a": {"x": 2, "y": 4},
         "d": {"y": 6},
         "m": {"x": 17},
         "n": {"q": 9}}
    r = {"a": {"x": 3, "y": 5},
         "d": {"x": 5},
         "m": {"x": 27},
         "n": {"q": 19}}
    decisions = decide_merge(b, l, r)
    # Note that "n":{} gets added to the merge result even though it's empty
    assert apply_decisions(b, decisions) == {
        "a": {"x": 1}, "d": {"x": 4, "y": 5}, "m": {"x": 7}}
    assert len(decisions) == 6
    assert all([d.conflict for d in decisions])
    assert decisions[0].common_path == ("m",)
    assert decisions[1].common_path == ("d",)
    assert decisions[2].common_path == ("d",)
    assert decisions[3].common_path == ("a",)
    assert decisions[4].common_path == ("a",)
    assert decisions[5].common_path == ()

    assert decisions[0].local_diff == [op_replace("x", 17)]
    assert decisions[0].remote_diff == [op_replace("x", 27)]

    assert decisions[1].local_diff == [op_remove("x")]
    assert decisions[1].remote_diff == [op_replace("x", 5)]

    assert decisions[2].local_diff == [op_replace("y", 6)]
    assert decisions[2].remote_diff == [op_remove("y")]

    assert decisions[3].local_diff == [op_replace("x", 2)]
    assert decisions[3].remote_diff == [op_replace("x", 3)]

    assert decisions[4].local_diff == [op_add("y", 4)]
    assert decisions[4].remote_diff == [op_add("y", 5)]

    assert decisions[5].local_diff == [op_add("n", {"q": 9})]
    assert decisions[5].remote_diff == [op_add("n", {"q": 19})]