Esempio n. 1
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)
            m, lc, rc = merge(b, l, r)
            assert m == l
            assert lc == []
            assert rc == []

    # 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)
            m, lc, rc = merge(b, l, r)
            assert m == r
            assert lc == []
            assert rc == []

    # 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
    m, lc, rc = merge(b, l, r)
    #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 lc == [op_addrange(0, l), op_removerange(0, 2)]
    assert rc == [op_addrange(0, r), op_removerange(0, 2)]
Esempio n. 2
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]
Esempio n. 3
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. 4
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. 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)
            m, lc, rc = merge(b, l, r)
            assert m == l
            assert lc == []
            assert rc == []

    # 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)
            m, lc, rc = merge(b, l, r)
            assert m == r
            assert lc == []
            assert rc == []

    # 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
    m, lc, rc = merge(b, l, r)
    #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 lc == [op_addrange(0, l), op_removerange(0, 2)]
    assert rc == [op_addrange(0, r), op_removerange(0, 2)]
Esempio n. 6
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]
Esempio n. 7
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"
Esempio n. 8
0
def test_patch_str():
    # Test +, single item insertion
    assert patch("42", [op_patch(0, [op_add(0, "3"), op_remove(1)])]) == "34"

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

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

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

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

    # Test --, sequence deletion
    assert patch("abcd", [op_patch(0, [op_removerange(0, 2)])]) == "cd"
    assert patch("abcd", [op_patch(0, [op_removerange(1, 2)])]) == "ad"
    assert patch("abcd", [op_patch(0, [op_removerange(2, 2)])]) == "ab"
Esempio n. 9
0
def test_merge_inserts_within_deleted_range():
    # Multiple inserts within a deleted range
    base = """
def f(x):
    return x**2

def g(x):
    return x + 2
"""

    # Insert foo and bar
    local = """
def foo(y):
    return y / 3

def f(x):
    return x**2

def bar(y):
    return y - 3

def g(x):
    return x + 2
"""

    remote = ""  # Delete all

    if 0:
        # This is quite optimistic and would require employing aggressive
        # attempts at automatic resolution beyond what git and meld do:
        expected_partial = """
def foo(y):
    return y / 3

def bar(y):
    return y - 3
"""
    else:
        expected_partial = base
        expected_conflicts = [{
            "common_path": ("cells", ),
            "local_diff": [
                op_patch("cells", [
                    op_addrange(0, [nbformat.v4.new_code_cell(local)]),
                    op_removerange(0, 1)
                ])
            ],
            "remote_diff": [
                op_patch("cells", [
                    op_addrange(0, [nbformat.v4.new_code_cell(remote)]),
                    op_removerange(0, 1)
                ])
            ]
        }]
    _check_sources(base, local, remote, expected_partial, expected_conflicts)

    # Keep it failing
    assert False
Esempio n. 10
0
def test_merge_conflicts_get_diff_indices_shifted():
    # Trying to induce conflicts with shifting of diff indices
    source = [
        "def foo(x, y):",
        "    z = x * y",
        "    return z",
        ]
    local = [["same"], source+["local"], ["different"]]
    base = [["same"], source+["base"], ["same"]]
    remote = [["different"], source+["remote"], ["same"]]
    expected_partial = [["different"],
                        source + ["local", "remote"],
                        ["different"]]
    expected_conflicts = [{
        "common_path": (),
        "local_diff": [
            op_removerange(1, 1),
            op_addrange(1, ["left"]),
        ],
        "remote_diff": [
            op_removerange(1, 1),
            op_addrange(1, ["right"]),
        ]
    }]
    _check_sources(base, local, remote, expected_partial, expected_conflicts)

    # Trying to induce conflicts with shifting of diff indices
    source = [
        "def foo(x, y):",
        "    z = x * y",
        "    return z",
        ]
    local = [["same"],
             source + ["long line with minor change L"],
             ["different"]]
    base = [["same"],
            source + ["long line with minor change"],
            ["same"]]
    remote = [["different"],
              source + ["long line with minor change R"],
              ["same"]]
    expected_partial = [["different"],
                        source + ["long line with minor change"],
                        ["different"]]
    expected_conflicts = [{
        "common_path": (),
        "local_diff": [
            op_removerange(1, 1),
            op_addrange(1, ["left"]),
        ],
        "remote_diff": [
            op_removerange(1, 1),
            op_addrange(1, ["right"]),
        ]
    }]
    _check_sources(base, local, remote, expected_partial, expected_conflicts)
Esempio n. 11
0
def test_merge_input_strategy_inline_source_conflict():
    # Conflicting cell inserts at same location as removing old cell
    local = [["local\n", "some other\n", "lines\n", "to align\n"]]
    base = [["base\n", "some other\n", "lines\n", "to align\n"]]
    remote = [["remote\n", "some other\n", "lines\n", "to align\n"]]
    # Ideal case:
    if have_git:
        expected_partial = [[
            "<<<<<<< local\n",
            "local\n",
            "=======\n",
            "remote\n",
            ">>>>>>> remote\n",
            "some other\n",
            "lines\n",
            "to align\n",
            ]]
    else:
        # Fallback is not very smart yet:
        expected_partial = [[
            "<<<<<<< local\n",
            "local\n",
            "some other\n",
            "lines\n",
            "to align\n",
            '||||||| base\n',
            'base\n',
            'some other\n',
            'lines\n',
            'to align\n',
            "=======\n",
            "remote\n",
            "some other\n",
            "lines\n",
            "to align\n",
            ">>>>>>> remote",
            ]]
    expected_conflicts = [{
        "common_path": ("cells", 0, "source"),
        "local_diff": [
            op_addrange(0, local[0][0:1]),
            op_removerange(0, 1)
            ],
        "remote_diff": [
            op_addrange(0, remote[0][0:1]),
            op_removerange(0, 1)
            ],
        "custom_diff": [
            op_addrange(0, expected_partial[0]),
            op_removerange(0, len(base[0]))
            ],
        }]
    merge_args = copy.deepcopy(args)
    merge_args.merge_strategy = "use-base"
    merge_args.input_strategy = "inline"
    _check_sources(base, local, remote, expected_partial, expected_conflicts, merge_args)
Esempio n. 12
0
def test_merge_conflicts_get_diff_indices_shifted():
    # Trying to induce conflicts with shifting of diff indices
    source = [
        "def foo(x, y):",
        "    z = x * y",
        "    return z",
        ]
    local = [["same"], source+["local"], ["different"]]
    base = [["same"], source+["base"], ["same"]]
    remote = [["different"], source+["remote"], ["same"]]
    expected_partial = [["different"],
                        source + ["local", "remote"],
                        ["different"]]
    expected_conflicts = [{
        "common_path": (),
        "local_diff": [
            op_removerange(1, 1),
            op_addrange(1, ["left"]),
        ],
        "remote_diff": [
            op_removerange(1, 1),
            op_addrange(1, ["right"]),
        ]
    }]
    _check_sources(base, local, remote, expected_partial, expected_conflicts)

    # Trying to induce conflicts with shifting of diff indices
    source = [
        "def foo(x, y):",
        "    z = x * y",
        "    return z",
        ]
    local = [["same"],
             source + ["long line with minor change L"],
             ["different"]]
    base = [["same"],
            source + ["long line with minor change"],
            ["same"]]
    remote = [["different"],
              source + ["long line with minor change R"],
              ["same"]]
    expected_partial = [["different"],
                        source + ["long line with minor change"],
                        ["different"]]
    expected_conflicts = [{
        "common_path": (),
        "local_diff": [
            op_removerange(1, 1),
            op_addrange(1, ["left"]),
        ],
        "remote_diff": [
            op_removerange(1, 1),
            op_addrange(1, ["right"]),
        ]
    }]
    _check_sources(base, local, remote, expected_partial, expected_conflicts)
Esempio n. 13
0
def test_merge_inserts_within_deleted_range():
    # Multiple inserts within a deleted range
    base = """
def f(x):
    return x**2

def g(x):
    return x + 2
"""

    # Insert foo and bar
    local = """
def foo(y):
    return y / 3

def f(x):
    return x**2

def bar(y):
    return y - 3

def g(x):
    return x + 2
"""

    remote = ""  # Delete all

    if 0:
        # This is quite optimistic and would require employing aggressive
        # attempts at automatic resolution beyond what git and meld do:
        expected_partial = """
def foo(y):
    return y / 3

def bar(y):
    return y - 3
"""
    else:
        expected_partial = base
        expected_conflicts = [{
            "common_path": ("cells",),
            "local_diff": [op_patch("cells", [op_addrange(0, [
                nbformat.v4.new_code_cell(local)]), op_removerange(0, 1)])],
            "remote_diff": [op_patch("cells", [op_addrange(0, [
                nbformat.v4.new_code_cell(remote)]), op_removerange(0, 1)])]
        }]
    _check_sources(base, local, remote, expected_partial, expected_conflicts)

    # Keep it failing
    assert False
Esempio n. 14
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. 15
0
def test_merge_conflicts_get_diff_indices_shifted():
    # Trying to induce conflicts with shifting of diff indices
    source = [
        "def foo(x, y):",
        "    z = x * y",
        "    return z",
    ]
    local = [["same"], source + ["local"], ["different"]]
    base = [["same"], source + ["base"], ["same"]]
    remote = [["different"], source + ["remote"], ["same"]]
    expected_partial = [["different"], source + ["local", "remote"],
                        ["different"]]
    expected_lco = [
        op_removerange(1, 1),
        op_addrange(1, ["left"]),
    ]
    expected_rco = [
        op_removerange(1, 1),
        op_addrange(1, ["right"]),
    ]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # Trying to induce conflicts with shifting of diff indices
    source = [
        "def foo(x, y):",
        "    z = x * y",
        "    return z",
    ]
    local = [["same"], source + ["long line with minor change L"],
             ["different"]]
    base = [["same"], source + ["long line with minor change"], ["same"]]
    remote = [["different"], source + ["long line with minor change R"],
              ["same"]]
    expected_partial = [["different"],
                        source + ["long line with minor change"],
                        ["different"]]
    expected_lco = [
        op_removerange(1, 1),
        op_addrange(1, ["left"]),  # todo
    ]
    expected_rco = [
        op_removerange(1, 1),
        op_addrange(1, ["right"]),  # todo
    ]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)
Esempio n. 16
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. 17
0
def test_merge_input_strategy_inline_source_conflict():
    # Conflicting cell inserts at same location as removing old cell
    local = [["local\n", "some other\n", "lines\n", "to align\n"]]
    base = [["base\n", "some other\n", "lines\n", "to align\n"]]
    remote = [["remote\n", "some other\n", "lines\n", "to align\n"]]
    # Ideal case:
    expected_partial = [[
       "<<<<<<< local\n",
       "local\n",
       #"||||||| base\n",
       #"base\n",
       "=======\n",
       "remote\n",
       ">>>>>>> remote\n",
       "some other\n",
       "lines\n",
       "to align\n",
       ]]
    # Current case:
    _expected_partial = [[
        "<<<<<<< local\n",
        "local\nsome other\nlines\nto align\n",
        "||||||| base\n",
        "base\nsome other\nlines\nto align\n",
        "=======\n",
        "remote\nsome other\nlines\nto align\n",
        ">>>>>>> remote"]]
    expected_conflicts = [{
        "common_path": ("cells", 0),
        "local_diff": [
            op_patch('source', [
                op_addrange(
                    0, local[0][0:1]),
                op_removerange(0, 1)],
            )],
        "remote_diff": [
            op_patch('source', [
                op_addrange(
                    0, remote[0][0:1]),
                op_removerange(0, 1)],

            )],
        }]
    merge_args = copy.deepcopy(args)
    merge_args.merge_strategy = "use-base"
    merge_args.input_strategy = "inline"
    _check_sources(base, local, remote, expected_partial, expected_conflicts, merge_args)
Esempio n. 18
0
def test_merge_conflicts_get_diff_indices_shifted():
    # Trying to induce conflicts with shifting of diff indices
    source = [
        "def foo(x, y):",
        "    z = x * y",
        "    return z",
        ]
    local  = [["same"], source+["local"], ["different"]]
    base   = [["same"], source+["base"], ["same"]]
    remote = [["different"], source+["remote"], ["same"]]
    expected_partial = [["different"], source+["local", "remote"], ["different"]]
    expected_lco = [
        op_removerange(1, 1),
        op_addrange(1, ["left"]),
        ]
    expected_rco = [
        op_removerange(1, 1),
        op_addrange(1, ["right"]),
        ]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)


    # Trying to induce conflicts with shifting of diff indices
    source = [
        "def foo(x, y):",
        "    z = x * y",
        "    return z",
        ]
    local  = [["same"], source+["long line with minor change L"], ["different"]]
    base   = [["same"], source+["long line with minor change"], ["same"]]
    remote = [["different"], source+["long line with minor change R"], ["same"]]
    expected_partial = [["different"], source+["long line with minor change"], ["different"]]
    expected_lco = [
        op_removerange(1, 1),
        op_addrange(1, ["left"]), # todo
        ]
    expected_rco = [
        op_removerange(1, 1),
        op_addrange(1, ["right"]), # todo
        ]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)
Esempio n. 19
0
def test_merge_simple_cell_source_conflicting_edit_aligned():
    # Conflicting edit in first line of single cell
    local = [["local\n", "some other\n", "lines\n", "to align\n"]]
    base = [["base\n", "some other\n", "lines\n", "to align\n"]]
    remote = [["remote\n", "some other\n", "lines\n", "to align\n"]]
    expected_partial = [["base\n", "some other\n", "lines\n", "to align\n"]]
    expected_conflicts = [{
        "common_path": ("cells", 0, "source"),
        "local_diff": [op_addrange(0, local[0][0:1]),
                       op_removerange(0, 1)],
        "remote_diff": [op_addrange(0, remote[0][0:1]),
                        op_removerange(0, 1)]
    }]
    merge_args = copy.deepcopy(args)
    merge_args.merge_strategy = "mergetool"

    _check_sources(base, local, remote, expected_partial, expected_conflicts,
                   merge_args)
Esempio n. 20
0
def test_merge_simple_cell_source_conflicting_edit_aligned():
    # Conflicting cell inserts at same location as removing old cell
    local = [["local\n", "some other\n", "lines\n", "to align\n"]]
    base = [["base\n", "some other\n", "lines\n", "to align\n"]]
    remote = [["remote\n", "some other\n", "lines\n", "to align\n"]]
    expected_partial = [["base\n", "some other\n", "lines\n", "to align\n"]]
    expected_conflicts = [{
        "common_path": ("cells", 0, "source"),
        "local_diff": [
            op_addrange(
                0, local[0][0:1]),
            op_removerange(0, 1)],
        "remote_diff": [
            op_addrange(
                0, remote[0][0:1]),
            op_removerange(0, 1)]
        }]
    merge_args = copy.deepcopy(args)
    merge_args.merge_strategy = "mergetool"
    _check_sources(base, local, remote, expected_partial, expected_conflicts, merge_args)
Esempio n. 21
0
def test_merge_simple_cell_sources():
    # A very basic test: Just checking changes to a single cell source,

    # No change
    local  = [["same"]]
    base   = [["same"]]
    remote = [["same"]]
    expected_partial = [["same"]]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # One sided change
    local  = [["same"]]
    base   = [["same"]]
    remote = [["different"]]
    expected_partial = [["different"]]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # One sided change
    local  = [["different"]]
    base   = [["same"]]
    remote = [["same"]]
    expected_partial = [["different"]]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # Same change on both sides
    local  = [["different"]]
    base   = [["same"]]
    remote = [["different"]]
    expected_partial = [["different"]]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # Conflicting cell inserts at same location as removing old cell
    local  = [["local"]]
    base   = [["base"]]
    remote = [["remote"]]
    expected_partial = [["base"]]
    expected_lco = [op_patch("cells", [
        op_addrange(0, [nbformat.v4.new_code_cell(source) for source in local]),
        op_removerange(0, 1)])]
    expected_rco = [op_patch("cells", [
        op_addrange(0, [nbformat.v4.new_code_cell(source) for source in remote]),
        op_removerange(0, 1)])]
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # Cell inserts at same location but no other modifications: should this be accepted?
    local  = [["base"], ["local"]]
    base   = [["base"]]
    remote = [["base"], ["remote"]]
    if 0:  # Treat as conflict
        expected_partial = [["base"]]
        expected_lco = [op_patch("cells", [
            op_addrange(1, [nbformat.v4.new_code_cell(source) for source in local]),
            ])]
        expected_rco = [op_patch("cells", [
            op_addrange(1, [nbformat.v4.new_code_cell(source) for source in remote]),
            ])]
    else:  # Treat as non-conflict (insert both)
        expected_partial = [["base"], ["local"], ["remote"]]
        expected_lco = []
        expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)
Esempio n. 22
0
def test_merge_simple_cell_sources():
    # A very basic test: Just checking changes to a single cell source,

    # No change
    local = [["same"]]
    base = [["same"]]
    remote = [["same"]]
    expected_partial = [["same"]]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # One sided change
    local = [["same"]]
    base = [["same"]]
    remote = [["different"]]
    expected_partial = [["different"]]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # One sided change
    local = [["different"]]
    base = [["same"]]
    remote = [["same"]]
    expected_partial = [["different"]]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # Same change on both sides
    local = [["different"]]
    base = [["same"]]
    remote = [["different"]]
    expected_partial = [["different"]]
    expected_lco = []
    expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # Conflicting cell inserts at same location as removing old cell
    local = [["local"]]
    base = [["base"]]
    remote = [["remote"]]
    expected_partial = [["base"]]
    expected_lco = [
        op_patch("cells", [
            op_addrange(
                0, [nbformat.v4.new_code_cell(source) for source in local]),
            op_removerange(0, 1)
        ])
    ]
    expected_rco = [
        op_patch("cells", [
            op_addrange(
                0, [nbformat.v4.new_code_cell(source) for source in remote]),
            op_removerange(0, 1)
        ])
    ]
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)

    # Cell inserts at same location but no other modifications: should this be accepted?
    local = [["base"], ["local"]]
    base = [["base"]]
    remote = [["base"], ["remote"]]
    if 0:  # Treat as conflict
        expected_partial = [["base"]]
        expected_lco = [
            op_patch("cells", [
                op_addrange(
                    1, [nbformat.v4.new_code_cell(source)
                        for source in local]),
            ])
        ]
        expected_rco = [
            op_patch("cells", [
                op_addrange(
                    1,
                    [nbformat.v4.new_code_cell(source) for source in remote]),
            ])
        ]
    else:  # Treat as non-conflict (insert both)
        expected_partial = [["base"], ["local"], ["remote"]]
        expected_lco = []
        expected_rco = []
    _check(base, local, remote, expected_partial, expected_lco, expected_rco)