def test_decide_merge_list_conflicting_insertions_in_chunks(): # Next, test when insertions DO chunk together: b = [1, 9] l = [1, 2, 7, 9] r = [1, 3, 7, 9] # Check strategyless resolution strategies = Strategies({}) resolved = decide_merge(b, l, r, strategies) expected_partial = [1, 7, 9] assert apply_decisions(b, resolved) == expected_partial strategies = Strategies({"/*": "use-local"}) resolved = decide_merge(b, l, r, strategies) assert apply_decisions(b, resolved) == l assert not any(d.conflict for d in resolved) strategies = Strategies({"/*": "use-remote"}) resolved = decide_merge(b, l, r, strategies) assert apply_decisions(b, resolved) == r assert not any(d.conflict for d in resolved) strategies = Strategies({"/*": "use-base"}) resolved = decide_merge(b, l, r, strategies) assert apply_decisions(b, resolved) == expected_partial assert not any(d.conflict for d in resolved) strategies = Strategies({"/": "clear-all"}) resolved = decide_merge(b, l, r, strategies) assert apply_decisions(b, resolved) == [] 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-parent"}) resolved = autoresolve(b, decisions, strategies) assert apply_decisions(b, resolved) == [] assert not any(d.conflict for d in resolved)
def test_decide_merge_simple_list_insert_conflict_resolution(): # local and remote adds an entry each b = [1] l = [1, 2] r = [1, 3] strategies = Strategies({"/*": "use-local"}) decisions = decide_merge(b, l, r, strategies) assert apply_decisions(b, decisions) == l assert not any(d.conflict for d in decisions) strategies = Strategies({"/*": "use-remote"}) decisions = decide_merge(b, l, r, strategies) assert apply_decisions(b, decisions) == r assert not any(d.conflict for d in decisions) strategies = Strategies({"/*": "use-base"}) decisions = decide_merge(b, l, r, strategies) assert apply_decisions(b, decisions) == b assert not any(d.conflict for d in decisions) strategies = Strategies({"/": "clear-all"}) decisions = decide_merge(b, l, r, strategies) assert apply_decisions(b, decisions) == [] assert not any(d.conflict for d in decisions)
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)
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_decide_merge_strategy_clear_all(): base = {"foo": [1, 2]} local = {"foo": [1, 4, 2]} remote = {"foo": [1, 3, 2]} strategies = Strategies({"/foo": "clear-all"}) decisions = decide_merge(base, local, remote, strategies) assert apply_decisions(base, decisions) == {"foo": []} base = {"foo": [1, 2]} local = {"foo": [1, 4, 2]} remote = {"foo": [1, 2, 3]} strategies = Strategies({"/foo": "clear-all"}) decisions = decide_merge(base, local, remote, strategies) assert apply_decisions(base, decisions) == {"foo": [1, 4, 2, 3]}
def test_decide_merge_strategy_remove(): base = {"foo": [1, 2]} local = {"foo": [1, 4, 2]} remote = {"foo": [1, 3, 2]} strategies = Strategies({"/foo": "remove"}) decisions = decide_merge(base, local, remote, strategies) assert apply_decisions(base, decisions) == {"foo": [1, 2]} assert decisions[0].local_diff != [] assert decisions[0].remote_diff != [] strategies = Strategies({}) decisions = decide_merge(base, local, remote, strategies) assert apply_decisions(base, decisions) == {"foo": [1, 2]} assert decisions[0].local_diff != [] assert decisions[0].remote_diff != []
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_decide_merge_strategy_fail(reset_log): """Check that "fail" strategy results in proper exception raised.""" # One level dict base = {"foo": 1} local = {"foo": 2} remote = {"foo": 3} strategies = Strategies({"/foo": "fail"}) with pytest.raises(RuntimeError): conflicted_decisions = decide_merge(base, local, remote, strategies) # Nested dicts base = {"foo": {"bar": 1}} local = {"foo": {"bar": 2}} remote = {"foo": {"bar": 3}} strategies = Strategies({"/foo/bar": "fail"}) with pytest.raises(RuntimeError): decisions = decide_merge(base, local, remote, strategies)
def test_decide_merge_dict_transients(): # Setup transient difference in base and local, deletion in remote b = {'a': {'transient': 22}} l = {'a': {'transient': 242}} r = {} # Assert that generic merge gives conflict strategies = Strategies() decisions = decide_merge(b, l, r, strategies) assert apply_decisions(b, decisions) == b assert len(decisions) == 1 assert decisions[0].conflict # Supply transient list to autoresolve, and check that transient is ignored strategies = Strategies(transients=['/a/transient']) decisions = decide_merge(b, l, r, strategies) assert apply_decisions(b, decisions) == r assert not any(d.conflict for d in decisions)
def test_decide_merge_strategy_clear2(): base = {"foo": "1"} local = {"foo": "2"} remote = {"foo": "3"} strategies = Strategies({"/foo": "clear"}) decisions = decide_merge(base, local, remote, strategies) #assert decisions == [] assert apply_decisions(base, decisions) == {"foo": ""} assert not any([d.conflict for d in decisions])
def test_decide_merge_simple_list_insert_conflict_resolution__union(): # local and remote adds an entry each b = [1] l = [1, 2] r = [1, 3] strategies = Strategies({"/": "union"}) decisions = decide_merge(b, l, r, strategies) assert apply_decisions(b, decisions) == [1, 2, 3] assert not any(d.conflict for d in decisions)
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-parent"}) 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_decide_merge_list_conflicting_insertions_in_chunks__union(): # Next, test when insertions DO chunk together: b = [1, 9] l = [1, 2, 7, 9] r = [1, 3, 7, 9] strategies = Strategies({"/": "union"}) resolved = decide_merge(b, l, r, strategies) assert apply_decisions(b, resolved) == [1, 2, 3, 7, 9] assert not any(d.conflict for d in resolved)
def merge_strategies(args): strategies = Strategies({ # Pick highest minor format: "/version": "take-max" }) ignore_transients = args.ignore_transients if args else True if ignore_transients: strategies.transients = [] strategies.update({}) # Get args, default to inline for cli tool, intended to produce # an editable notebook that can be manually edited merge_strategy = args.merge_strategy if args else None # Set root strategy strategies["/"] = merge_strategy return strategies
def test_decide_merge_strategy_clear1(): """Check strategy "clear" in various cases.""" # One level dict, clearing item value (think foo==execution_count) base = {"foo": 1} local = {"foo": 2} remote = {"foo": 3} strategies = Strategies({"/foo": "clear"}) decisions = decide_merge(base, local, remote, strategies) assert apply_decisions(base, decisions) == {"foo": None} assert not any([d.conflict for d in decisions])
def test_decide_merge_list_conflicting_insertions_separate_chunks__union(): # 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] strategies = Strategies({"/": "union"}) resolved = decide_merge(b, l, r, strategies) assert apply_decisions(b, resolved) == [1, 2, 3, 9, 11] 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_decide_merge_list_conflicting_insertions_separate_chunks_v2(): # 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] # Check strategyless resolution strategies = Strategies({}) resolved = decide_merge(b, l, r, 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
def test_decide_merge_list_conflicting_insertions_separate_chunks_v1(): # 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] # Check strategyless resolution strategies = Strategies({}) resolved = decide_merge(b, l, r, 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 = decide_merge(b, l, r, strategies) assert apply_decisions(b, resolved) == l assert not any(d.conflict for d in resolved) strategies = Strategies({"/*": "use-remote"}) resolved = decide_merge(b, l, r, strategies) assert apply_decisions(b, resolved) == r assert not any(d.conflict for d in resolved) strategies = Strategies({"/*": "use-base"}) resolved = decide_merge(b, l, r, 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({"/": "clear-all"}) resolved = decide_merge(b, l, r, strategies) assert apply_decisions(b, resolved) == [] assert not any(d.conflict for d in resolved)
def test_decide_merge_strategy_use_foo_on_dict_items(): base = {"foo": 1} local = {"foo": 2} remote = {"foo": 3} strategies = Strategies({"/foo": "use-base"}) decisions = decide_merge(base, local, remote, strategies) assert not any([d.conflict for d in decisions]) assert apply_decisions(base, decisions) == {"foo": 1} strategies = Strategies({"/foo": "use-local"}) decisions = decide_merge(base, local, remote, strategies) assert not any([d.conflict for d in decisions]) assert apply_decisions(base, decisions) == {"foo": 2} strategies = Strategies({"/foo": "use-remote"}) decisions = decide_merge(base, local, remote, strategies) assert not any([d.conflict for d in decisions]) assert apply_decisions(base, decisions) == {"foo": 3} base = {"foo": {"bar": 1}} local = {"foo": {"bar": 2}} remote = {"foo": {"bar": 3}} strategies = Strategies({"/foo/bar": "use-base"}) decisions = decide_merge(base, local, remote, strategies) assert not any([d.conflict for d in decisions]) assert apply_decisions(base, decisions) == {"foo": {"bar": 1}} strategies = Strategies({"/foo/bar": "use-local"}) decisions = decide_merge(base, local, remote, strategies) assert not any([d.conflict for d in decisions]) assert apply_decisions(base, decisions) == {"foo": {"bar": 2}} strategies = Strategies({"/foo/bar": "use-remote"}) decisions = decide_merge(base, local, remote, strategies) assert not any([d.conflict for d in decisions]) assert apply_decisions(base, decisions) == {"foo": {"bar": 3}}
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-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 = 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-parent"}) resolved = autoresolve(b, decisions, strategies) assert apply_decisions(b, resolved) == [] assert not any(d.conflict for d in resolved)