Ejemplo n.º 1
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.º 2
0
def test_pop_patch_unpoppable():
    md = MergeDecision(common_path=("a", "b"),
                       action="base",
                       conflict=True,
                       local_diff=[op_remove("c")],
                       remote_diff=[op_patch("c", [op_remove("d")])])
    dec = pop_patch_decision(md)
    assert dec is None
Ejemplo n.º 3
0
def test_merge_builder_ensures_common_path():
    b = MergeDecisionBuilder()
    b.conflict(("a", "b"), [op_patch("c", [op_remove("d")])],
               [op_patch("c", [op_remove("e")])])
    assert len(b.decisions) == 1
    assert b.decisions[0].common_path == ("a", "b", "c")
    assert b.decisions[0].local_diff == [op_remove("d")]
    assert b.decisions[0].remote_diff == [op_remove("e")]
Ejemplo n.º 4
0
def test_merge_builder_ensures_common_path():
    b = MergeDecisionBuilder()
    b.conflict(("a", "b"),
               [op_patch("c", [op_remove("d")])],
               [op_patch("c", [op_remove("e")])])
    assert len(b.decisions) == 1
    assert b.decisions[0].common_path == ("a", "b", "c")
    assert b.decisions[0].local_diff == [op_remove("d")]
    assert b.decisions[0].remote_diff == [op_remove("e")]
Ejemplo n.º 5
0
def test_pop_patch_unpoppable():
    md = MergeDecision(
        common_path=("a", "b"),
        action="base",
        conflict=True,
        local_diff=[op_remove("c")],
        remote_diff=[op_patch("c", [op_remove("d")])]
    )
    dec = pop_patch_decision(md)
    assert dec is None
Ejemplo n.º 6
0
def test_pop_patch_single_level():
    md = MergeDecision(common_path=("a", "b"),
                       action="base",
                       conflict=True,
                       local_diff=[op_patch("c", [op_remove("d")])],
                       remote_diff=[op_patch("c", [op_remove("e")])])
    dec = pop_patch_decision(md)
    assert dec is not None
    assert dec.common_path == ("a", "b", "c")
    assert dec.local_diff == [op_remove("d")]
    assert dec.remote_diff == [op_remove("e")]
Ejemplo n.º 7
0
def test_pop_patch_single_level():
    md = MergeDecision(
        common_path=("a", "b"),
        action="base",
        conflict=True,
        local_diff=[op_patch("c", [op_remove("d")])],
        remote_diff=[op_patch("c", [op_remove("e")])]
    )
    dec = pop_patch_decision(md)
    assert dec is not None
    assert dec.common_path == ("a", "b", "c")
    assert dec.local_diff == [op_remove("d")]
    assert dec.remote_diff == [op_remove("e")]
Ejemplo n.º 8
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.º 9
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.º 10
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.º 11
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.º 12
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.º 13
0
def test_build_diffs_unsorted():
    b = MergeDecisionBuilder()
    b.onesided((), [op_remove('a')], None)
    b.onesided(('b',), [op_remove('j')], None)
    b.onesided(('c',), [op_remove('k')], None)
    b.onesided(('d',), [op_remove('l')], None)
    base = dict(a=1, b=dict(i=2), c=dict(j=3), d=dict(k=4))
    diff = build_diffs(base, b.decisions, 'local')
    assert len(diff) == 4
    assert diff[0] == op_remove('a')
    assert diff[1] == op_patch('b', [op_remove('j')])
    assert diff[2] == op_patch('c', [op_remove('k')])
    assert diff[3] == op_patch('d', [op_remove('l')])
Ejemplo n.º 14
0
def test_build_diffs_unsorted():
    b = MergeDecisionBuilder()
    b.onesided((), [op_remove('a')], None)
    b.onesided(('b',), [op_remove('j')], None)
    b.onesided(('c',), [op_remove('k')], None)
    b.onesided(('d',), [op_remove('l')], None)
    base = dict(a=1, b=dict(i=2), c=dict(j=3), d=dict(k=4))
    diff = build_diffs(base, b.decisions, 'local')
    assert len(diff) == 4
    assert diff[0] == op_remove('a')
    assert diff[1] == op_patch('b', [op_remove('j')])
    assert diff[2] == op_patch('c', [op_remove('k')])
    assert diff[3] == op_patch('d', [op_remove('l')])
Ejemplo n.º 15
0
def test_ensure_common_path_multilevel_intermediate():
    diffs = [[op_patch("c", [op_patch("d", [op_remove("f")])])],
             [op_patch("c", [op_patch("e", [op_remove("g")])])]]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b", "c"), [[op_patch("d", [op_remove("f")])],
                                     [op_patch("e", [op_remove("g")])]])
Ejemplo n.º 16
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.º 17
0
def test_ensure_common_path_one_sided_remote():
    diffs = [[op_patch("c", [op_remove("d")])],
             []]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b", "c"), [[op_remove("d")], None])
Ejemplo n.º 18
0
def test_ensure_common_path_one_sided_empty():
    diffs = [[],
             [op_patch("c", [op_remove("d")])]]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b", "c"), [None, [op_remove("d")]])
Ejemplo n.º 19
0
def test_ensure_common_path_multilevel_intermediate():
    diffs = [[op_patch("c", [op_patch("d", [op_remove("f")])])],
             [op_patch("c", [op_patch("e", [op_remove("g")])])]]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b", "c"), [[op_patch("d", [op_remove("f")])],
                                     [op_patch("e", [op_remove("g")])]])
Ejemplo n.º 20
0
def test_ensure_common_path_single_level():
    diffs = [[op_patch("c", [op_remove("e")])],
             [op_patch("c", [op_remove("d")])]]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b", "c"), [[op_remove("e")], [op_remove("d")]])
Ejemplo n.º 21
0
def test_ensure_common_path_no_change():
    diffs = [[op_remove("c")], [op_patch("c", [op_remove("d")])]]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b"), diffs)
Ejemplo n.º 22
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.º 23
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.º 24
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.º 25
0
def test_ensure_common_path_single_level():
    diffs = [[op_patch("c", [op_remove("e")])],
             [op_patch("c", [op_remove("d")])]]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b", "c"), [[op_remove("e")], [op_remove("d")]])
Ejemplo n.º 26
0
def test_ensure_common_path_no_change():
    diffs = [[op_remove("c")], [op_patch("c", [op_remove("d")])]]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b"), diffs)
Ejemplo n.º 27
0
def test_ensure_common_path_one_sided_empty():
    diffs = [[], [op_patch("c", [op_remove("d")])]]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b", "c"), [None, [op_remove("d")]])
Ejemplo n.º 28
0
def test_ensure_common_path_one_sided_remote():
    diffs = [[op_patch("c", [op_remove("d")])], []]
    res = ensure_common_path(("a", "b"), diffs)
    assert res == (("a", "b", "c"), [[op_remove("d")], None])
Ejemplo n.º 29
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.º 30
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})]