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)
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)
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)
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])
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}}
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)
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) decisions = decide_merge_with_diff( base, local, remote, local_diffs, remote_diffs) strategies = Strategies() decisions = autoresolve(base, decisions, 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) decisions = decide_merge_with_diff( base, local, remote, local_diffs, remote_diffs) strategies = Strategies() decisions = autoresolve(base, decisions, strategies) partial = apply_decisions(base, decisions) _check(partial, expected_partial, decisions, expected_conflicts)
def xtest_autoresolve_use_one(): # These are reused in all tests below args = None base = { "foo": 1 } local = { "foo": 2 } remote = { "foo": 3 } strategies = { "/foo": "use-base" } merged, local_conflicts, remote_conflicts = autoresolve(base, diff(base, local), diff(base, remote), args, strategies, "") assert local_conflicts == [] assert remote_conflicts == [] assert merged == { "foo": 1 } strategies = { "/foo": "use-local" } merged, local_conflicts, remote_conflicts = autoresolve(base, diff(base, local), diff(base, remote), args, strategies, "") assert merged == { "foo": 2 } assert local_conflicts == [] assert remote_conflicts == [] strategies = { "/foo": "use-remote" } merged, local_conflicts, remote_conflicts = autoresolve(base, diff(base, local), diff(base, remote), args, strategies, "") assert merged == { "foo": 3 } assert local_conflicts == [] assert remote_conflicts == []
def test_autoresolve_dict_fail(): """Check that "fail" strategy results in proper exception raised.""" strategies = {"/foo": "fail"} with pytest.raises(RuntimeError): autoresolve(base, conflicted_decisions, strategies) base2 = {"foo": {"bar": 1}} local2 = {"foo": {"bar": 2}} remote2 = {"foo": {"bar": 3}} strategies = {"/foo/bar": "fail"} decisions = decide_merge(base2, local2, remote2) with pytest.raises(RuntimeError): autoresolve(base2, decisions, strategies) strategies = {"/foo": "fail"} with pytest.raises(RuntimeError): autoresolve(base2, decisions, strategies)
def test_autoresolve_dict_fail(): """Check that "fail" strategy results in proper exception raised.""" strategies = Strategies({"/foo": "fail"}) with pytest.raises(RuntimeError): autoresolve(base, conflicted_decisions, strategies) base2 = {"foo": {"bar": 1}} local2 = {"foo": {"bar": 2}} remote2 = {"foo": {"bar": 3}} strategies = Strategies({"/foo/bar": "fail"}) decisions = decide_merge(base2, local2, remote2) with pytest.raises(RuntimeError): autoresolve(base2, decisions, strategies) strategies = Strategies({"/foo": "fail"}) with pytest.raises(RuntimeError): autoresolve(base2, decisions, strategies)
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)
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)