Esempio n. 1
0
def test_autoresolve_list_conflicting_insertions_simple():
    # local and remote adds an entry each
    b = [1]
    l = [1, 2]
    r = [1, 3]
    decisions = decide_merge(b, l, r)

    strategies = {"/*": "use-local"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == l
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "use-remote"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "use-base"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == b
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "join"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == [1, 2, 3]
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "clear-parent"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == []
    assert not any(d.conflict for d in resolved)
Esempio n. 2
0
def test_autoresolve_mixed_nested_transients():
    # For this test, we need to use a custom predicate to ensure alignment
    common = {'id': 'This ensures alignment'}
    predicates = defaultdict(lambda: [operator.__eq__], {
        '/': [lambda a, b: a['id'] == b['id']],
    })
    # Setup transient difference in base and local, deletion in remote
    b = [{'a': {'transient': 22}}]
    l = [{'a': {'transient': 242}}]
    b[0].update(common)
    l[0].update(common)
    r = []

    # Make decisions based on diffs with predicates
    ld = diff(b, l, path="", predicates=predicates)
    rd = diff(b, r, path="", predicates=predicates)
    decisions = decide_merge_with_diff(b, l, r, ld, rd)

    # Assert that generic merge gives conflict
    assert apply_decisions(b, decisions) == b
    assert len(decisions) == 1
    assert decisions[0].conflict

    # Without strategy, no progress is made:
    resolved = autoresolve(b, decisions, Strategies())
    assert resolved == decisions

    # Supply transient list to autoresolve, and check that transient is ignored
    strategies = Strategies(transients=[
        '/*/a/transient'
    ])
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)
Esempio n. 3
0
def test_autoresolve_list_conflicting_insertions_simple():
    # local and remote adds an entry each
    b = [1]
    l = [1, 2]
    r = [1, 3]
    decisions = decide_merge(b, l, r)

    strategies = Strategies({"/*": "use-local"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == l
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "use-remote"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "use-base"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == b
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "union"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == [1, 2, 3]
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "clear-all"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == []
    assert not any(d.conflict for d in resolved)
Esempio n. 4
0
def test_autoresolve_mixed_nested_transients():
    # For this test, we need to use a custom predicate to ensure alignment
    common = {'id': 'This ensures alignment'}
    predicates = defaultdict(lambda: [operator.__eq__], {
        '/': [lambda a, b: a['id'] == b['id']],
    })
    # Setup transient difference in base and local, deletion in remote
    b = [{'a': {'transient': 22}}]
    l = [{'a': {'transient': 242}}]
    b[0].update(common)
    l[0].update(common)
    r = []

    # Make decisions based on diffs with predicates
    ld = diff(b, l, path="", predicates=predicates)
    rd = diff(b, r, path="", predicates=predicates)
    decisions = decide_merge_with_diff(b, l, r, ld, rd)

    # Assert that generic merge gives conflict
    assert apply_decisions(b, decisions) == b
    assert len(decisions) == 1
    assert decisions[0].conflict

    # Without strategy, no progress is made:
    resolved = autoresolve(b, decisions, Strategies())
    assert resolved == decisions

    # Supply transient list to autoresolve, and check that transient is ignored
    strategies = Strategies(transients=['/*/a/transient'])
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)
Esempio n. 5
0
def test_deep_merge_lists_delete_no_conflict():
    # local removes an entry
    b = [[1, 3, 5], [2, 4, 6]]
    for i in range(len(b)):
        for j in range(len(b[i])):
            l = copy.deepcopy(b)
            r = copy.deepcopy(b)
            l[i].pop(j)
            decisions = decide_merge(b, l, r)
            assert apply_decisions(b, decisions) == l
            assert not any([d.conflict for d in decisions])

    # remote removes an entry
    b = [[1, 3, 5], [2, 4, 6]]
    for i in range(len(b)):
        for j in range(len(b[i])):
            l = copy.deepcopy(b)
            r = copy.deepcopy(b)
            r[i].pop(j)
            decisions = decide_merge(b, l, r)
            assert apply_decisions(b, decisions) == r
            assert not any([d.conflict for d in decisions])

    # both remove the same entry and one each
    b = [[1, 3, 5], [2, 4, 6]]
    l = [[1, 5], [2, 4]]  # deletes 3 and 6
    r = [[1, 5], [4, 6]]  # deletes 3 and 2
    decisions = decide_merge(b, l, r)
    m = apply_decisions(b, decisions)
    #assert m == [[1, 5], [2, 4], [1, 5], [4, 6]]  # This was expected behaviour before: clear b, add l, add r
    #assert m == [[1, 5], [4]]  # 2,3,6 should be gone. TODO: This is the naively ideal thought-reading behaviour. Possible?
    assert m == b  # conflicts lead to original kept in m
    assert decisions[0].conflict
    assert decisions[0].local_diff == [op_addrange(0, l), op_removerange(0, 2)]
    assert decisions[0].remote_diff == [op_addrange(0, r), op_removerange(0, 2)]
Esempio n. 6
0
def test_deep_merge_dicts_delete_no_conflict():
    # local removes an entry
    b = {"p": {"b": 1, "a": 3}}
    l = {"p": {"b": 1}}
    r = {"p": {"b": 1, "a": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1}}
    assert not any([d.conflict for d in decisions])

    # remote removes an entry
    b = {"p": {"b": 1, "a": 3}}
    l = {"p": {"b": 1, "a": 3}}
    r = {"p": {"b": 1}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1}}
    assert not any([d.conflict for d in decisions])

    # both remove the same entry
    b = {"p": {"b": 1, "a": 3}}
    l = {"p": {"b": 1}}
    r = {"p": {"b": 1}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1}}
    assert not any([d.conflict for d in decisions])

    # both remove the same entry and one each
    b = {"p": {"b": 1, "a": 3, "c": 5, "d": 7}}
    l = {"p": {"b": 1, "c": 5}}
    r = {"p": {"b": 1, "d": 7}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1}}
    assert not any([d.conflict for d in decisions])
Esempio n. 7
0
def test_autoresolve_dict_transients():
    # Setup transient difference in base and local, deletion in remote
    b = {'a': {'transient': 22}}
    l = {'a': {'transient': 242}}
    r = {}

    # Make decisions based on diffs with predicates
    decisions = decide_merge(b, l, r)

    # Assert that generic merge gives conflict
    assert apply_decisions(b, decisions) == b
    assert len(decisions) == 1
    assert decisions[0].conflict

    # Without strategy, no progress is made:
    resolved = autoresolve(b, decisions, Strategies())
    assert resolved == decisions

    # Supply transient list to autoresolve, and check that transient is ignored
    strategies = Strategies(transients=[
        '/a/transient'
    ])
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)
Esempio n. 8
0
def test_deep_merge_lists_delete_no_conflict():
    # local removes an entry
    b = [[1, 3, 5], [2, 4, 6]]
    for i in range(len(b)):
        for j in range(len(b[i])):
            l = copy.deepcopy(b)
            r = copy.deepcopy(b)
            l[i].pop(j)
            decisions = decide_merge(b, l, r)
            assert apply_decisions(b, decisions) == l
            assert not any([d.conflict for d in decisions])

    # remote removes an entry
    b = [[1, 3, 5], [2, 4, 6]]
    for i in range(len(b)):
        for j in range(len(b[i])):
            l = copy.deepcopy(b)
            r = copy.deepcopy(b)
            r[i].pop(j)
            decisions = decide_merge(b, l, r)
            assert apply_decisions(b, decisions) == r
            assert not any([d.conflict for d in decisions])

    # both remove the same entry and one each
    b = [[1, 3, 5], [2, 4, 6]]
    l = [[1, 5], [2, 4]]  # deletes 3 and 6
    r = [[1, 5], [4, 6]]  # deletes 3 and 2
    decisions = decide_merge(b, l, r)
    m = apply_decisions(b, decisions)
    #assert m == [[1, 5], [2, 4], [1, 5], [4, 6]]  # This was expected behaviour before: clear b, add l, add r
    #assert m == [[1, 5], [4]]  # 2,3,6 should be gone. TODO: This is the naively ideal thought-reading behaviour. Possible?
    assert m == b  # conflicts lead to original kept in m
    assert decisions[0].conflict
    assert decisions[0].local_diff == [op_addrange(0, l), op_removerange(0, 2)]
    assert decisions[0].remote_diff == [op_addrange(0, r), op_removerange(0, 2)]
Esempio n. 9
0
def test_deep_merge_dicts_delete_no_conflict():
    # local removes an entry
    b = {"p": {"b": 1, "a": 3}}
    l = {"p": {"b": 1}}
    r = {"p": {"b": 1, "a": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1}}
    assert not any([d.conflict for d in decisions])

    # remote removes an entry
    b = {"p": {"b": 1, "a": 3}}
    l = {"p": {"b": 1, "a": 3}}
    r = {"p": {"b": 1}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1}}
    assert not any([d.conflict for d in decisions])

    # both remove the same entry
    b = {"p": {"b": 1, "a": 3}}
    l = {"p": {"b": 1}}
    r = {"p": {"b": 1}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1}}
    assert not any([d.conflict for d in decisions])

    # both remove the same entry and one each
    b = {"p": {"b": 1, "a": 3, "c": 5, "d": 7}}
    l = {"p": {"b": 1, "c": 5}}
    r = {"p": {"b": 1, "d": 7}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1}}
    assert not any([d.conflict for d in decisions])
Esempio n. 10
0
def test_deep_merge_dicts_insert_no_conflict():
    # local adds an entry
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1, "l": 2}}
    r = {"p": {"b": 1}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1, "l": 2}}
    assert not any([d.conflict for d in decisions])

    # remote adds an entry
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1}}
    r = {"p": {"b": 1, "r": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1, "r": 3}}
    assert not any([d.conflict for d in decisions])

    # local and remote adds an entry each
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1, "l": 2}}
    r = {"p": {"b": 1, "r": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1, "l": 2, "r": 3}}
    assert not any([d.conflict for d in decisions])

    # local and remote adds the same entry plus an entry each
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1, "s": 7, "l": 2}}
    r = {"p": {"b": 1, "s": 7, "r": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {
        "p": {"b": 1, "s": 7, "l": 2, "r": 3}}
    assert not any([d.conflict for d in decisions])
Esempio n. 11
0
def test_autoresolve_dict_transients():
    # For this test, we need to use a custom predicate to ensure alignment

    common = {'id': 'This ensures alignment'}
    # Setup transient difference in base and local, deletion in remote
    b = {'a': {'transient': 22}}
    l = {'a': {'transient': 242}}
    r = {}

    # Make decisions based on diffs with predicates
    decisions = decide_merge(b, l, r)

    # Assert that generic merge gives conflict
    assert apply_decisions(b, decisions) == b
    assert len(decisions) == 1
    assert decisions[0].conflict

    # Without strategy, no progress is made:
    resolved = autoresolve(b, decisions, Strategies())
    assert resolved == decisions

    # Supply transient list to autoresolve, and check that transient is ignored
    strategies = Strategies(transients=['/a/transient'])
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)
Esempio n. 12
0
def test_shallow_merge_lists_insert_no_conflict():
    # local adds an entry
    b = [1]
    l = b + [2]
    r = copy.deepcopy(b)
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1, 2]
    assert not any([d.conflict for d in decisions])

    # remote adds an entry
    b = [1]
    l = copy.deepcopy(b)
    r = b + [3]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1, 3]
    assert not any([d.conflict for d in decisions])

    # local and remote adds the same entries interleaved within each base entry
    b = [1, 3, 5]
    l = [0, 1, 2, 3, 4, 5]
    r = copy.deepcopy(l)
    decisions = decide_merge(b, l, r)

    assert apply_decisions(b, decisions) == l
    assert not any([d.conflict for d in decisions])
Esempio n. 13
0
def test_deep_merge_dicts_insert_no_conflict():
    # local adds an entry
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1, "l": 2}}
    r = {"p": {"b": 1}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1, "l": 2}}
    assert not any([d.conflict for d in decisions])

    # remote adds an entry
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1}}
    r = {"p": {"b": 1, "r": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1, "r": 3}}
    assert not any([d.conflict for d in decisions])

    # local and remote adds an entry each
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1, "l": 2}}
    r = {"p": {"b": 1, "r": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"p": {"b": 1, "l": 2, "r": 3}}
    assert not any([d.conflict for d in decisions])

    # local and remote adds the same entry plus an entry each
    b = {"p": {"b": 1}}
    l = {"p": {"b": 1, "s": 7, "l": 2}}
    r = {"p": {"b": 1, "s": 7, "r": 3}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {
        "p": {"b": 1, "s": 7, "l": 2, "r": 3}}
    assert not any([d.conflict for d in decisions])
Esempio n. 14
0
def test_shallow_merge_dicts_insert_no_conflict():
    # local adds an entry
    b = {"b": 1}
    l = {"b": 1, "l": 2}
    r = {"b": 1}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1, "l": 2}
    assert not any([d.conflict for d in decisions])

    # remote adds an entry
    b = {"b": 1}
    l = {"b": 1}
    r = {"b": 1, "r": 3}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1, "r": 3}
    assert not any([d.conflict for d in decisions])

    # local and remote adds an entry each
    b = {"b": 1}
    l = {"b": 1, "l": 2}
    r = {"b": 1, "r": 3}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1, "l": 2, "r": 3}
    assert not any([d.conflict for d in decisions])

    # local and remote adds an equal entry plus a different entry each
    b = {"b": 1}
    l = {"b": 1, "l": 2, "s": 7}
    r = {"b": 1, "r": 3, "s": 7}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1, "l": 2, "r": 3, "s": 7}
    assert not any([d.conflict for d in decisions])
Esempio n. 15
0
def test_shallow_merge_dicts_insert_no_conflict():
    # local adds an entry
    b = {"b": 1}
    l = {"b": 1, "l": 2}
    r = {"b": 1}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1, "l": 2}
    assert not any([d.conflict for d in decisions])

    # remote adds an entry
    b = {"b": 1}
    l = {"b": 1}
    r = {"b": 1, "r": 3}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1, "r": 3}
    assert not any([d.conflict for d in decisions])

    # local and remote adds an entry each
    b = {"b": 1}
    l = {"b": 1, "l": 2}
    r = {"b": 1, "r": 3}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1, "l": 2, "r": 3}
    assert not any([d.conflict for d in decisions])

    # local and remote adds an equal entry plus a different entry each
    b = {"b": 1}
    l = {"b": 1, "l": 2, "s": 7}
    r = {"b": 1, "r": 3, "s": 7}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1, "l": 2, "r": 3, "s": 7}
    assert not any([d.conflict for d in decisions])
Esempio n. 16
0
def test_shallow_merge_lists_insert_no_conflict():
    # local adds an entry
    b = [1]
    l = b + [2]
    r = copy.deepcopy(b)
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1, 2]
    assert not any([d.conflict for d in decisions])

    # remote adds an entry
    b = [1]
    l = copy.deepcopy(b)
    r = b + [3]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1, 3]
    assert not any([d.conflict for d in decisions])

    # local and remote adds the same entries interleaved within each base entry
    b = [1, 3, 5]
    l = [0, 1, 2, 3, 4, 5]
    r = copy.deepcopy(l)
    decisions = decide_merge(b, l, r)

    assert apply_decisions(b, decisions) == l
    assert not any([d.conflict for d in decisions])
Esempio n. 17
0
def test_deep_merge_onesided_inner_list_insert_no_conflict():
    # local adds an entry
    b = [[1]]
    l = [[1, 2]]
    r = [[1]]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [[1, 2]]
    assert not any([d.conflict for d in decisions])

    # remote adds an entry
    b = [[1]]
    l = [[1]]
    r = [[1, 3]]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [[1, 3]]
    assert not any([d.conflict for d in decisions])
Esempio n. 18
0
def test_deep_merge_onesided_inner_list_insert_no_conflict():
    # local adds an entry
    b = [[1]]
    l = [[1, 2]]
    r = [[1]]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [[1, 2]]
    assert not any([d.conflict for d in decisions])

    # remote adds an entry
    b = [[1]]
    l = [[1]]
    r = [[1, 3]]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [[1, 3]]
    assert not any([d.conflict for d in decisions])
Esempio n. 19
0
def test_shallow_merge_lists_delete_no_conflict():
    # local removes an entry
    b = [1, 3]
    l = [1]
    r = [1, 3]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1]
    assert not any([d.conflict for d in decisions])

    # remote removes an entry
    b = [1, 3]
    l = [1, 3]
    r = [1]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1]
    assert not any([d.conflict for d in decisions])

    # both remove the same entry
    b = [1, 3, 2, 7]
    for i in range(len(b)):
        l = copy.deepcopy(b)
        r = copy.deepcopy(b)
        l.pop(i)
        r.pop(i)
        decisions = decide_merge(b, l, r)
        e = copy.deepcopy(b)
        e.pop(i)
        assert apply_decisions(b, decisions) == e
        assert not any([d.conflict for d in decisions])

    # both remove the same entry plus one other each
    b = [1, 3, 2, 7]
    for i in range(len(b)):
        for j in range(len(b)):
            if j == i:
                continue
            for k in range(len(b)):
                if k == i or k == j:
                    continue
                l = cut(b, i, j)
                r = cut(b, i, k)
                e = cut(b, i, j, k)
                decisions = decide_merge(b, l, r)
                assert apply_decisions(b, decisions) == e
                assert not any([d.conflict for d in decisions])
Esempio n. 20
0
def test_shallow_merge_lists_delete_no_conflict():
    # local removes an entry
    b = [1, 3]
    l = [1]
    r = [1, 3]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1]
    assert not any([d.conflict for d in decisions])

    # remote removes an entry
    b = [1, 3]
    l = [1, 3]
    r = [1]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1]
    assert not any([d.conflict for d in decisions])

    # both remove the same entry
    b = [1, 3, 2, 7]
    for i in range(len(b)):
        l = copy.deepcopy(b)
        r = copy.deepcopy(b)
        l.pop(i)
        r.pop(i)
        decisions = decide_merge(b, l, r)
        e = copy.deepcopy(b)
        e.pop(i)
        assert apply_decisions(b, decisions) == e
        assert not any([d.conflict for d in decisions])

    # both remove the same entry plus one other each
    b = [1, 3, 2, 7]
    for i in range(len(b)):
        for j in range(len(b)):
            if j == i:
                continue
            for k in range(len(b)):
                if k == i or k == j:
                    continue
                l = cut(b, i, j)
                r = cut(b, i, k)
                e = cut(b, i, j, k)
                decisions = decide_merge(b, l, r)
                assert apply_decisions(b, decisions) == e
                assert not any([d.conflict for d in decisions])
Esempio n. 21
0
def test_autoresolve_dict_clear():
    """Check strategy "clear" in various cases."""

    base2 = {"foo": [1, 2]}
    local2 = {"foo": [1, 4, 2]}
    remote2 = {"foo": [1, 3, 2]}
    decisions = decide_merge(base2, local2, remote2)
    assert apply_decisions(base2, decisions) == {"foo": [1, 2]}
    assert decisions[0].local_diff != []
    assert decisions[0].remote_diff != []
    strategies = {"/foo": "clear-parent"}
    resolved = autoresolve(base2, decisions, strategies)
    assert apply_decisions(base2, resolved) == {"foo": []}
    assert not any([d.conflict for d in resolved])

    strategies = {"/foo": "clear"}
    resolved = autoresolve(base2, decisions, strategies)
    assert apply_decisions(base2, resolved) == {"foo": [1, None]}
    assert not any([d.conflict for d in resolved])
Esempio n. 22
0
def test_autoresolve_dict_clear():
    """Check strategy "clear" in various cases."""

    base2 = {"foo": [1, 2]}
    local2 = {"foo": [1, 4, 2]}
    remote2 = {"foo": [1, 3, 2]}
    decisions = decide_merge(base2, local2, remote2)
    assert apply_decisions(base2, decisions) == {"foo": [1, 2]}
    assert decisions[0].local_diff != []
    assert decisions[0].remote_diff != []
    strategies = Strategies({"/foo": "clear-all"})
    resolved = autoresolve(base2, decisions, strategies)
    assert apply_decisions(base2, resolved) == {"foo": []}
    assert not any([d.conflict for d in resolved])

    strategies = Strategies({"/foo": "clear"})
    resolved = autoresolve(base2, decisions, strategies)
    assert apply_decisions(base2, resolved) == {"foo": [1, None]}
    assert not any([d.conflict for d in resolved])
Esempio n. 23
0
def test_merge_nonconflicting_nested_dicts():
    # local and remote each adds, deletes, and modifies entries inside nested structure without conflicts
    b = {"a": {}, "d": {"x": 4, "y": 5, "z": 6}, "m": {"x": 7, "y": 8, "z": 9}}
    l = {"a": {"x": 1, "y": 2}, "d": {"z": 6}, "m": {"x": 17, "y": 18, "z": 9}}
    r = {"a": {"x": 1, "z": 3}, "d": {"y": 5}, "m": {"x": 17, "y": 8, "z": 19}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {
        "a": {"x": 1, "y": 2, "z": 3},
        "d": {},
        "m": {"x": 17, "y": 18, "z": 19}}
    assert not any([d.conflict for d in decisions])
Esempio n. 24
0
def test_merge_nonconflicting_nested_dicts():
    # local and remote each adds, deletes, and modifies entries inside nested structure without conflicts
    b = {"a": {}, "d": {"x": 4, "y": 5, "z": 6}, "m": {"x": 7, "y": 8, "z": 9}}
    l = {"a": {"x": 1, "y": 2}, "d": {"z": 6}, "m": {"x": 17, "y": 18, "z": 9}}
    r = {"a": {"x": 1, "z": 3}, "d": {"y": 5}, "m": {"x": 17, "y": 8, "z": 19}}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {
        "a": {"x": 1, "y": 2, "z": 3},
        "d": {},
        "m": {"x": 17, "y": 18, "z": 19}}
    assert not any([d.conflict for d in decisions])
Esempio n. 25
0
def test_autoresolve_dict_use_one_side():
    strategies = Strategies({"/foo": "use-base"})
    decisions = autoresolve(base, conflicted_decisions, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base, decisions) == {"foo": 1}

    strategies = Strategies({"/foo": "use-local"})
    decisions = autoresolve(base, conflicted_decisions, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base, decisions) == {"foo": 2}

    strategies = Strategies({"/foo": "use-remote"})
    decisions = autoresolve(base, conflicted_decisions, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base, decisions) == {"foo": 3}

    base2 = {"foo": {"bar": 1}}
    local2 = {"foo": {"bar": 2}}
    remote2 = {"foo": {"bar": 3}}
    conflicted_decisions2 = decide_merge(base2, local2, remote2)

    strategies = Strategies({"/foo/bar": "use-base"})
    decisions = autoresolve(base2, conflicted_decisions2, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base2, decisions) == {"foo": {"bar": 1}}

    strategies = Strategies({"/foo/bar": "use-local"})
    decisions = autoresolve(base2, conflicted_decisions2, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base2, decisions) == {"foo": {"bar": 2}}

    strategies = Strategies({"/foo/bar": "use-remote"})
    decisions = autoresolve(base2, conflicted_decisions2, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base2, decisions) == {"foo": {"bar": 3}}
Esempio n. 26
0
def test_autoresolve_dict_use_one_side():
    strategies = {"/foo": "use-base"}
    decisions = autoresolve(base, conflicted_decisions, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base, decisions) == {"foo": 1}

    strategies = {"/foo": "use-local"}
    decisions = autoresolve(base, conflicted_decisions, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base, decisions) == {"foo": 2}

    strategies = {"/foo": "use-remote"}
    decisions = autoresolve(base, conflicted_decisions, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base, decisions) == {"foo": 3}

    base2 = {"foo": {"bar": 1}}
    local2 = {"foo": {"bar": 2}}
    remote2 = {"foo": {"bar": 3}}
    conflicted_decisions2 = decide_merge(base2, local2, remote2)

    strategies = {"/foo/bar": "use-base"}
    decisions = autoresolve(base2, conflicted_decisions2, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base2, decisions) == {"foo": {"bar": 1}}

    strategies = {"/foo/bar": "use-local"}
    decisions = autoresolve(base2, conflicted_decisions2, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base2, decisions) == {"foo": {"bar": 2}}

    strategies = {"/foo/bar": "use-remote"}
    decisions = autoresolve(base2, conflicted_decisions2, strategies)
    assert not any([d.conflict for d in decisions])
    assert apply_decisions(base2, decisions) == {"foo": {"bar": 3}}
Esempio n. 27
0
def test_autoresolve_empty_strategies():
    """Check that a autoresolve works with empty strategies"""
    expected_partial_source = [["base\n", "some other\n", "lines\n", "to align\n"]]
    expected_partial_metadata = [{'myval': 'base'}]
    expected_partial_outputs = [["base\nsome other\nlines\nto align\n", "output2", "output3"]]
    base, local, remote, expected_partial = _make_notebook_with_multi_conflicts(
        expected_partial_source, expected_partial_metadata, expected_partial_outputs
    )

    expected_conflicts = [
        {
            'action': 'base',
            'common_path': ('cells', 0, 'source'),
            'conflict': True,
            'local_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['local\n']},
                           {'key': 0, 'length': 1, 'op': 'removerange'}],
            'remote_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['remote\n']},
                            {'key': 0, 'length': 1, 'op': 'removerange'}]
        },
        {
            'action': 'base',
            'common_path': ('cells', 0, 'outputs', 0, 'data', 'text/plain'),
            'conflict': True,
            'local_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['local\n']},
                           {'key': 0, 'length': 1, 'op': 'removerange'}],
            'remote_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['remote\n']},
                            {'key': 0, 'length': 1, 'op': 'removerange'}]
        },
        {
            'action': 'base',
            'common_path': ('cells', 0, 'metadata', 'myval'),
            'conflict': True,
            'local_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['local']},
                           {'key': 0, 'length': 1, 'op': 'removerange'}],
            'remote_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['remote']},
                            {'key': 0, 'length': 1, 'op': 'removerange'}]
        }
    ]

    # Since we cannot pass directly a strategies object, include copy of relevant code:
    local_diffs = diff_notebooks(base, local)
    remote_diffs = diff_notebooks(base, remote)

    strategies = Strategies()
    decisions = decide_merge_with_diff(
        base, local, remote,
        local_diffs, remote_diffs,
        strategies)

    partial = apply_decisions(base, decisions)

    _check(partial, expected_partial, decisions, expected_conflicts)
def test_autoresolve_empty_strategies():
    """Check that a autoresolve works with empty strategies"""
    expected_partial_source = [["base\n", "some other\n", "lines\n", "to align\n"]]
    expected_partial_metadata = [{'myval': 'base'}]
    expected_partial_outputs = [["base\nsome other\nlines\nto align\n", "output2", "output3"]]
    base, local, remote, expected_partial = _make_notebook_with_multi_conflicts(
        expected_partial_source, expected_partial_metadata, expected_partial_outputs
    )

    expected_conflicts = [
        {
            'action': 'base',
            'common_path': ('cells', 0, 'source'),
            'conflict': True,
            'local_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['local\n']},
                           {'key': 0, 'length': 1, 'op': 'removerange'}],
            'remote_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['remote\n']},
                            {'key': 0, 'length': 1, 'op': 'removerange'}]
        },
        {
            'action': 'base',
            'common_path': ('cells', 0, 'outputs', 0, 'data', 'text/plain'),
            'conflict': True,
            'local_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['local\n']},
                           {'key': 0, 'length': 1, 'op': 'removerange'}],
            'remote_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['remote\n']},
                            {'key': 0, 'length': 1, 'op': 'removerange'}]
        },
        {
            'action': 'base',
            'common_path': ('cells', 0, 'metadata', 'myval'),
            'conflict': True,
            'local_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['local']},
                           {'key': 0, 'length': 1, 'op': 'removerange'}],
            'remote_diff': [{'key': 0, 'op': 'addrange', 'valuelist': ['remote']},
                            {'key': 0, 'length': 1, 'op': 'removerange'}]
        }
    ]

    # Since we cannot pass directly a strategies object, include copy of relevant code:
    local_diffs = diff_notebooks(base, local)
    remote_diffs = diff_notebooks(base, remote)

    strategies = Strategies()
    decisions = decide_merge_with_diff(
        base, local, remote,
        local_diffs, remote_diffs,
        strategies)

    partial = apply_decisions(base, decisions)

    _check(partial, expected_partial, decisions, expected_conflicts)
Esempio n. 29
0
def test_deep_merge_lists_delete_no_conflict__currently_expected_failures():
    # both remove the same entry
    b = [[1, 3, 5], [2, 4, 6]]
    for i in range(len(b)):
        for j in range(len(b[i])):
            l = copy.deepcopy(b)
            r = copy.deepcopy(b)
            l[i].pop(j)
            r[i].pop(j)
            assert l == r
            decisions = decide_merge(b, l, r)
            assert apply_decisions(b, decisions) == r
            assert not any([d.conflict for d in decisions])
Esempio n. 30
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"])]
Esempio n. 31
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"])]
Esempio n. 32
0
def test_deep_merge_lists_delete_no_conflict__currently_expected_failures():
    # both remove the same entry
    b = [[1, 3, 5], [2, 4, 6]]
    for i in range(len(b)):
        for j in range(len(b[i])):
            l = copy.deepcopy(b)
            r = copy.deepcopy(b)
            l[i].pop(j)
            r[i].pop(j)
            assert l == r
            decisions = decide_merge(b, l, r)
            assert apply_decisions(b, decisions) == r
            assert not any([d.conflict for d in decisions])
Esempio n. 33
0
def test_deep_merge_lists_insert_conflicted():

    # Some notes explaining the below expected values... while this works:
    assert diff([1], [1, 2]) == [op_addrange(1, [2])]
    # This does not happen:
    #assert diff([[1]], [[1, 2]]) == [op_patch(0, [op_addrange(1, [2])])]
    # Instead we get this:
    assert diff([[1]],
                [[1, 2]]) == [op_addrange(0, [[1, 2]]),
                              op_removerange(0, 1)]
    # To get the "patch inner list" version instead of the "remove inner list + add new inner list" version,
    # the diff algorithm would need to identify that the inner list [1] is similar to [1,2],
    # e.g. through heuristics. In the case [1] vs [1,2] the answer will probably be "not similar enough" even
    # with better heuristics than we have today, i.e. we can never be quite certain what the "right choice" is.

    # *** Because of this uncertainty, insertions at the same location are suspect and must be treated as conflicts! ***

    # local and remote adds an entry each to inner list
    # (documents failure to identify inner list patching opportunity)
    b = [[1]]
    l = [[1, 2]]
    r = [[1, 3]]
    decisions = decide_merge(b, l, r)
    #assert apply_decisions(b, decisions) == [[1, 2], [1, 3]]  # This was expected behaviour in old code, obviously not what we want
    #assert apply_decisions(b, decisions) == [[1, 2, 3]]  # This is the behaviour we want from an ideal thought-reading algorithm, unclear if possible
    #assert apply_decisions(b, decisions) == [[1]]  # This is the behaviour we get if reverts to base value
    assert len(decisions) == 1
    d = decisions[0]
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(0, [[1, 2]]), op_removerange(0, 1)]
    assert d.remote_diff == [op_addrange(0, [[1, 3]]), op_removerange(0, 1)]

    # local and remote adds the same entry plus an entry each
    b = [[1]]
    l = [[1, 2, 4]]
    r = [[1, 3, 4]]
    decisions = decide_merge(b, l, r)
    # No identification of equal inserted value 4 expected from current algorithm
    #assert apply_decisions(b, decisions) == [[1, 2, 4, 3, 4]]  # TODO: Is this the behaviour we want, merge in inner list?
    #assert apply_decisions(b, decisions) == [[1, 2, 4], [1, 3, 4]]  # This was expected behaviour in previous algorithm
    #assert lc == []
    #assert rc == []
    assert apply_decisions(b, decisions) == [[
        1
    ]]  # This is expected behaviour today, base left for conflict resolution
    assert len(decisions) == 1
    d = decisions[0]
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(0, [[1, 2, 4]]), op_removerange(0, 1)]
    assert d.remote_diff == [op_addrange(0, [[1, 3, 4]]), op_removerange(0, 1)]
Esempio n. 34
0
def test_shallow_merge_dicts_delete_no_conflict():
    # local removes an entry
    b = {"b": 1, "a": 3}
    l = {"b": 1}
    r = {"b": 1, "a": 3}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1}
    assert not any([d.conflict for d in decisions])

    # remote removes an entry
    b = {"b": 1, "a": 3}
    l = {"b": 1, "a": 3}
    r = {"b": 1}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1}
    assert not any([d.conflict for d in decisions])

    # both remove the same entry
    b = {"b": 1, "a": 3}
    l = {"b": 1}
    r = {"b": 1}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1}
    assert not any([d.conflict for d in decisions])
Esempio n. 35
0
def test_shallow_merge_dicts_delete_no_conflict():
    # local removes an entry
    b = {"b": 1, "a": 3}
    l = {"b": 1}
    r = {"b": 1, "a": 3}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1}
    assert not any([d.conflict for d in decisions])

    # remote removes an entry
    b = {"b": 1, "a": 3}
    l = {"b": 1, "a": 3}
    r = {"b": 1}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1}
    assert not any([d.conflict for d in decisions])

    # both remove the same entry
    b = {"b": 1, "a": 3}
    l = {"b": 1}
    r = {"b": 1}
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == {"b": 1}
    assert not any([d.conflict for d in decisions])
Esempio n. 36
0
def test_deep_merge_twosided_inserts_conflicted2():
    # local and remote adds an entry each in a new sublist
    b = [[1]]
    l = [[1], [2], [3]]
    r = [[1], [2], [4]]
    assert diff(b, l) == [op_addrange(1, [[2], [3]])]
    assert diff(b, r) == [op_addrange(1, [[2], [4]])]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [[1], [2]]
    assert len(decisions) == 2
    assert_either_decision(decisions[0], [op_addrange(1, [[2]])])
    d = decisions[1]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(1, l[2:])]
    assert d.remote_diff == [op_addrange(1, r[2:])]
Esempio n. 37
0
def test_deep_merge_twosided_inserts_conflicted2():
    # local and remote adds an entry each in a new sublist
    b = [[1]]
    l = [[1], [2], [3]]
    r = [[1], [2], [4]]
    assert diff(b, l) == [op_addrange(1, [[2], [3]])]
    assert diff(b, r) == [op_addrange(1, [[2], [4]])]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [[1], [2]]
    assert len(decisions) == 2
    assert_either_decision(decisions[0], [op_addrange(1, [[2]])])
    d = decisions[1]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(1, l[2:])]
    assert d.remote_diff == [op_addrange(1, r[2:])]
Esempio n. 38
0
def test_deep_merge_lists_insert_conflicted():

    # Some notes explaining the below expected values... while this works:
    assert diff([1], [1, 2]) == [op_addrange(1, [2])]
    # This does not happen:
    #assert diff([[1]], [[1, 2]]) == [op_patch(0, [op_addrange(1, [2])])]
    # Instead we get this:
    assert diff([[1]], [[1, 2]]) == [op_addrange(0, [[1, 2]]), op_removerange(0, 1)]
    # To get the "patch inner list" version instead of the "remove inner list + add new inner list" version,
    # the diff algorithm would need to identify that the inner list [1] is similar to [1,2],
    # e.g. through heuristics. In the case [1] vs [1,2] the answer will probably be "not similar enough" even
    # with better heuristics than we have today, i.e. we can never be quite certain what the "right choice" is.

    # *** Because of this uncertainty, insertions at the same location are suspect and must be treated as conflicts! ***


    # local and remote adds an entry each to inner list
    # (documents failure to identify inner list patching opportunity)
    b = [[1]]
    l = [[1, 2]]
    r = [[1, 3]]
    decisions = decide_merge(b, l, r)
    #assert apply_decisions(b, decisions) == [[1, 2], [1, 3]]  # This was expected behaviour in old code, obviously not what we want
    #assert apply_decisions(b, decisions) == [[1, 2, 3]]  # This is the behaviour we want from an ideal thought-reading algorithm, unclear if possible
    #assert apply_decisions(b, decisions) == [[1]]  # This is the behaviour we get if reverts to base value
    assert len(decisions) == 1
    d = decisions[0]
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(0, [[1, 2]]), op_removerange(0, 1)]
    assert d.remote_diff == [op_addrange(0, [[1, 3]]), op_removerange(0, 1)]

    # local and remote adds the same entry plus an entry each
    b = [[1]]
    l = [[1, 2, 4]]
    r = [[1, 3, 4]]
    decisions = decide_merge(b, l, r)
    # No identification of equal inserted value 4 expected from current algorithm
    #assert apply_decisions(b, decisions) == [[1, 2, 4, 3, 4]]  # TODO: Is this the behaviour we want, merge in inner list?
    #assert apply_decisions(b, decisions) == [[1, 2, 4], [1, 3, 4]]  # This was expected behaviour in previous algorithm
    #assert lc == []
    #assert rc == []
    assert apply_decisions(b, decisions) == [[1]]  # This is expected behaviour today, base left for conflict resolution
    assert len(decisions) == 1
    d = decisions[0]
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(0, [[1, 2, 4]]), op_removerange(0, 1)]
    assert d.remote_diff == [op_addrange(0, [[1, 3, 4]]), op_removerange(0, 1)]
Esempio n. 39
0
def test_deep_merge_twosided_inserts_conflicted():
    # local and remote adds an entry each in a new sublist
    b = []
    l = [[2], [3]]
    r = [[2], [4]]
    assert diff(b, l) == [op_addrange(0, [[2], [3]])]
    assert diff(b, r) == [op_addrange(0, [[2], [4]])]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [[2]]
    assert len(decisions) == 2
    d = decisions[0]
    assert not d.conflict
    assert d.common_path == ()
    assert d.action == 'either'
    assert d.local_diff == [op_addrange(0, [[2]])]
    assert d.remote_diff == [op_addrange(0, [[2]])]
    d = decisions[1]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(0, [[3]])]
    assert d.remote_diff == [op_addrange(0, [[4]])]
Esempio n. 40
0
def test_deep_merge_twosided_inserts_conflicted():
    # local and remote adds an entry each in a new sublist
    b = []
    l = [[2], [3]]
    r = [[2], [4]]
    assert diff(b, l) == [op_addrange(0, [[2], [3]])]
    assert diff(b, r) == [op_addrange(0, [[2], [4]])]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [[2]]
    assert len(decisions) == 2
    d = decisions[0]
    assert not d.conflict
    assert d.common_path == ()
    assert d.action == 'either'
    assert d.local_diff == [op_addrange(0, [[2]])]
    assert d.remote_diff == [op_addrange(0, [[2]])]
    d = decisions[1]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(0, [[3]])]
    assert d.remote_diff == [op_addrange(0, [[4]])]
Esempio n. 41
0
def test_shallow_merge_lists_insert_conflicted():
    # local and remote adds an entry each
    b = [1]
    l = [1, 2]
    r = [1, 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_addrange(1, [2])]
    assert d.remote_diff == [op_addrange(1, [3])]

    # local and remote adds an equal entry plus a different entry each
    b = [1, 9]
    l = [1, 2, 7, 9]
    r = [1, 3, 7, 9]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1, 7, 9]
    assert len(decisions) == 2
    d = decisions[0]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(1, [2])]
    assert d.remote_diff == [op_addrange(1, [3])]
    d = decisions[1]
    assert d.common_path == ()
    assert_either_decision(d, [op_addrange(1, [7])])

    # local and remote adds entries to empty base
    b = []
    l = [1, 2, 4]
    r = [1, 3, 4]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1, 4]
    assert len(decisions) == 3
    d = decisions[0]
    assert d.common_path == ()
    assert_either_decision(d, [op_addrange(0, [1])])
    d = decisions[1]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(0, [2])]
    assert d.remote_diff == [op_addrange(0, [3])]
    d = decisions[2]
    assert d.common_path == ()
    assert_either_decision(d, [op_addrange(0, [4])])

    # local and remote adds different entries interleaved within each base entry
    b = [2, 5, 8]
    l = [0, 2, 3, 5, 6, 8, 9]
    r = [1, 2, 4, 5, 7, 8, 10]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == b
    assert len(decisions) == 4
    assert all([d.conflict for d in decisions])
    assert all([d.common_path == () for d in decisions])
    assert decisions[0].local_diff == [op_addrange(0, [0])]
    assert decisions[0].remote_diff == [op_addrange(0, [1])]
    assert decisions[1].local_diff == [op_addrange(1, [3])]
    assert decisions[1].remote_diff == [op_addrange(1, [4])]
    assert decisions[2].local_diff == [op_addrange(2, [6])]
    assert decisions[2].remote_diff == [op_addrange(2, [7])]
    assert decisions[3].local_diff == [op_addrange(3, [9])]
    assert decisions[3].remote_diff == [op_addrange(3, [10])]
Esempio n. 42
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})]
Esempio n. 43
0
def test_autoresolve_list_conflicting_insertions_mixed():
    # local and remote adds an equal entry plus a different entry each
    # First, test when insertions DO NOT chunk together:
    b = [1, 9]
    l = [1, 2, 9, 11]
    r = [1, 3, 9, 11]
    decisions = decide_merge(b, l, r)

    # Check strategyless resolution
    strategies = {}
    resolved = autoresolve(b, decisions, strategies)
    expected_partial = [1, 9, 11]
    assert apply_decisions(b, resolved) == expected_partial
    assert len(resolved) == 2
    assert resolved[0].conflict
    assert not resolved[1].conflict

    strategies = {"/*": "use-local"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == l
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "use-remote"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "use-base"}
    resolved = autoresolve(b, decisions, strategies)
    # Strategy is only applied to conflicted decisions:
    assert apply_decisions(b, resolved) == expected_partial
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "join"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == [1, 2, 3, 9, 11]
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "clear-parent"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == []
    assert not any(d.conflict for d in resolved)

    # Next, test when insertions DO chunk together:
    b = [1, 9]
    l = [1, 2, 7, 9]
    r = [1, 3, 7, 9]
    decisions = decide_merge(b, l, r)

    # Check strategyless resolution
    strategies = {}
    resolved = autoresolve(b, decisions, strategies)
    expected_partial = [1, 7, 9]
    assert apply_decisions(b, resolved) == expected_partial
    assert resolved == decisions  # Not able to resolve anything

    strategies = {"/*": "use-local"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == l
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "use-remote"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "use-base"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == expected_partial
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "join"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == [1, 2, 3, 7, 9]
    assert not any(d.conflict for d in resolved)

    strategies = {"/*": "clear-parent"}
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == []
    assert not any(d.conflict for d in resolved)
Esempio n. 44
0
def test_shallow_merge_lists_insert_conflicted():
    # local and remote adds an entry each
    b = [1]
    l = [1, 2]
    r = [1, 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_addrange(1, [2])]
    assert d.remote_diff == [op_addrange(1, [3])]

    # local and remote adds an equal entry plus a different entry each
    b = [1, 9]
    l = [1, 2, 7, 9]
    r = [1, 3, 7, 9]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1, 7, 9]
    assert len(decisions) == 2
    d = decisions[0]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(1, [2])]
    assert d.remote_diff == [op_addrange(1, [3])]
    d = decisions[1]
    assert d.common_path == ()
    assert_either_decision(d, [op_addrange(1, [7])])

    # local and remote adds entries to empty base
    b = []
    l = [1, 2, 4]
    r = [1, 3, 4]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == [1, 4]
    assert len(decisions) == 3
    d = decisions[0]
    assert d.common_path == ()
    assert_either_decision(d, [op_addrange(0, [1])])
    d = decisions[1]
    assert d.conflict
    assert d.common_path == ()
    assert d.local_diff == [op_addrange(0, [2])]
    assert d.remote_diff == [op_addrange(0, [3])]
    d = decisions[2]
    assert d.common_path == ()
    assert_either_decision(d, [op_addrange(0, [4])])

    # local and remote adds different entries interleaved within each base entry
    b = [2, 5, 8]
    l = [0, 2, 3, 5, 6, 8, 9]
    r = [1, 2, 4, 5, 7, 8, 10]
    decisions = decide_merge(b, l, r)
    assert apply_decisions(b, decisions) == b
    assert len(decisions) == 4
    assert all([d.conflict for d in decisions])
    assert all([d.common_path == () for d in decisions])
    assert decisions[0].local_diff == [op_addrange(0, [0])]
    assert decisions[0].remote_diff == [op_addrange(0, [1])]
    assert decisions[1].local_diff == [op_addrange(1, [3])]
    assert decisions[1].remote_diff == [op_addrange(1, [4])]
    assert decisions[2].local_diff == [op_addrange(2, [6])]
    assert decisions[2].remote_diff == [op_addrange(2, [7])]
    assert decisions[3].local_diff == [op_addrange(3, [9])]
    assert decisions[3].remote_diff == [op_addrange(3, [10])]
Esempio n. 45
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})]
Esempio n. 46
0
def test_autoresolve_list_conflicting_insertions_mixed():
    # local and remote adds an equal entry plus a different entry each
    # First, test when insertions DO NOT chunk together:
    b = [1, 9]
    l = [1, 2, 9, 11]
    r = [1, 3, 9, 11]
    decisions = decide_merge(b, l, r)

    # Check strategyless resolution
    strategies = Strategies({})
    resolved = autoresolve(b, decisions, strategies)
    expected_partial = [1, 9, 11]
    assert apply_decisions(b, resolved) == expected_partial
    assert len(resolved) == 2
    assert resolved[0].conflict
    assert not resolved[1].conflict

    strategies = Strategies({"/*": "use-local"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == l
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "use-remote"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "use-base"})
    resolved = autoresolve(b, decisions, strategies)
    # Strategy is only applied to conflicted decisions:
    assert apply_decisions(b, resolved) == expected_partial
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "union"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == [1, 2, 3, 9, 11]
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "clear-all"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == []
    assert not any(d.conflict for d in resolved)

    # Next, test when insertions DO chunk together:
    b = [1, 9]
    l = [1, 2, 7, 9]
    r = [1, 3, 7, 9]
    decisions = decide_merge(b, l, r)

    # Check strategyless resolution
    strategies = Strategies({})
    resolved = autoresolve(b, decisions, strategies)
    expected_partial = [1, 7, 9]
    assert apply_decisions(b, resolved) == expected_partial
    assert resolved == decisions  # Not able to resolve anything

    strategies = Strategies({"/*": "use-local"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == l
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "use-remote"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == r
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "use-base"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == expected_partial
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "union"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == [1, 2, 3, 7, 9]
    assert not any(d.conflict for d in resolved)

    strategies = Strategies({"/*": "clear-all"})
    resolved = autoresolve(b, decisions, strategies)
    assert apply_decisions(b, resolved) == []
    assert not any(d.conflict for d in resolved)