diff --git a/nbdime/tests/test_merge_notebooks.py b/nbdime/tests/test_merge_notebooks.py index aa8ae2ec..c5c9e360 100644 --- a/nbdime/tests/test_merge_notebooks.py +++ b/nbdime/tests/test_merge_notebooks.py @@ -11,7 +11,7 @@ import nbformat from nbdime.diff_format import op_patch, op_addrange, op_removerange, op_replace -from .utils import sources_to_notebook, outputs_to_notebook, have_git +from .utils import sources_to_notebook, outputs_to_notebook, have_git, strip_cell_ids, new_cell_wo_id from nbdime.nbmergeapp import _build_arg_parser from nbdime import merge_notebooks, apply_decisions from nbdime.diffing.notebooks import diff_notebooks, set_notebook_diff_targets @@ -56,15 +56,15 @@ def test_autoresolve_notebook_ec(): def test_merge_cell_sources_neighbouring_inserts(): - base = sources_to_notebook([[ + base = strip_cell_ids(sources_to_notebook([[ "def f(x):", " return x**2", ], [ "def g(y):", " return y + 2", ], - ]) - local = sources_to_notebook([[ + ])) + local = strip_cell_ids(sources_to_notebook([[ "def f(x):", " return x**2", ], [ @@ -73,8 +73,8 @@ def test_merge_cell_sources_neighbouring_inserts(): "def g(y):", " return y + 2", ], - ]) - remote = sources_to_notebook([[ + ])) + remote = strip_cell_ids(sources_to_notebook([[ "def f(x):", " return x**2", ], [ @@ -83,7 +83,7 @@ def test_merge_cell_sources_neighbouring_inserts(): "def g(y):", " return y + 2", ], - ]) + ])) if 1: expected_partial = base expected_conflicts = [{ @@ -114,15 +114,15 @@ def test_merge_cell_sources_neighbouring_inserts(): def test_merge_cell_sources_separate_inserts(): - base = sources_to_notebook([[ + base = strip_cell_ids(sources_to_notebook([[ "def f(x):", " return x**2", ], [ "def g(y):", " return y + 2", ], - ]) - local = sources_to_notebook([[ + ])) + local = strip_cell_ids(sources_to_notebook([[ "print(f(3))", ], [ "def f(x):", @@ -131,8 +131,8 @@ def test_merge_cell_sources_separate_inserts(): "def g(y):", " return y + 2", ], - ]) - remote = sources_to_notebook([[ + ])) + remote = strip_cell_ids(sources_to_notebook([[ "def f(x):", " return x**2", ], [ @@ -141,8 +141,8 @@ def test_merge_cell_sources_separate_inserts(): ], [ "print(f(7))", ], - ]) - expected = sources_to_notebook([[ + ])) + expected = strip_cell_ids(sources_to_notebook([[ "print(f(3))", ], [ "def f(x):", @@ -153,13 +153,13 @@ def test_merge_cell_sources_separate_inserts(): ], [ "print(f(7))", ], - ]) + ])) actual, decisions = merge_notebooks(base, local, remote, args) assert not any([d.conflict for d in decisions]) assert actual == expected -def src2nb(src): +def src2nb(src, strip_ids=False): """Convert source strings to a notebook. src is either a single multiline string to become one cell, @@ -171,6 +171,8 @@ def src2nb(src): src = sources_to_notebook(src) assert isinstance(src, dict) assert "cells" in src + if strip_ids: + strip_cell_ids(src) return src @@ -193,11 +195,11 @@ def _check(partial, expected_partial, decisions, expected_conflicts): assert d[k] == e[k] -def _check_sources(base, local, remote, expected_partial, expected_conflicts, merge_args=None): - base = src2nb(base) - local = src2nb(local) - remote = src2nb(remote) - expected_partial = src2nb(expected_partial) +def _check_sources(base, local, remote, expected_partial, expected_conflicts, merge_args=None, ignore_cell_ids=False): + base = src2nb(base, strip_ids=ignore_cell_ids) + local = src2nb(local, strip_ids=ignore_cell_ids) + remote = src2nb(remote, strip_ids=ignore_cell_ids) + expected_partial = src2nb(expected_partial, strip_ids=ignore_cell_ids) merge_args = merge_args or args partial, decisions = merge_notebooks(base, local, remote, merge_args) @@ -205,11 +207,11 @@ def _check_sources(base, local, remote, expected_partial, expected_conflicts, me _check(partial, expected_partial, decisions, expected_conflicts) -def _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args=None): - base = outputs_to_notebook(base) - local = outputs_to_notebook(local) - remote = outputs_to_notebook(remote) - expected_partial = outputs_to_notebook(expected_partial) +def _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args=None, ignore_cell_ids=False): + base = outputs_to_notebook(base, strip_ids=ignore_cell_ids) + local = outputs_to_notebook(local, strip_ids=ignore_cell_ids) + remote = outputs_to_notebook(remote, strip_ids=ignore_cell_ids) + expected_partial = outputs_to_notebook(expected_partial, strip_ids=ignore_cell_ids) merge_args = merge_args or args partial, decisions = merge_notebooks(base, local, remote, merge_args) @@ -293,10 +295,10 @@ def test_merge_simple_cell_source_conflicting_insert(): expected_conflicts = [{ "common_path": ("cells",), "local_diff": [op_addrange( - 1, [nbformat.v4.new_code_cell(local[1][0])]), + 1, [new_cell_wo_id(local[1][0])]), ], "remote_diff": [op_addrange( - 1, [nbformat.v4.new_code_cell(remote[1][0])]), + 1, [new_cell_wo_id(remote[1][0])]), ] }] else: # Treat as non-conflict (insert both) @@ -306,7 +308,7 @@ def test_merge_simple_cell_source_conflicting_insert(): merge_args = copy.deepcopy(args) merge_args.merge_strategy = "mergetool" - _check_sources(base, local, remote, expected_partial, expected_conflicts, merge_args) + _check_sources(base, local, remote, expected_partial, expected_conflicts, merge_args, True) @pytest.mark.xfail @@ -381,7 +383,7 @@ def test_merge_insert_cells_around_conflicting_cell(): expected_conflicts = [{ "common_path": ("cells",), "local_diff": [ - op_addrange(0, [nbformat.v4.new_code_cell( + op_addrange(0, [new_cell_wo_id( source=["new local cell"])]), op_patch(0, [op_patch("source", [ op_addrange(len("".join(source)), "local\n")])]), @@ -389,7 +391,7 @@ def test_merge_insert_cells_around_conflicting_cell(): "remote_diff": [op_patch(0, [op_patch('source', [ op_addrange(len("".join(source)), "remote\n")])])] }] - _check_sources(base, local, remote, expected_partial, expected_conflicts, merge_args) + _check_sources(base, local, remote, expected_partial, expected_conflicts, merge_args, True) @pytest.mark.xfail @@ -669,7 +671,7 @@ def test_merge_output_strategy_local_conflict(): expected_conflicts = [] merge_args = copy.deepcopy(args) merge_args.output_strategy = "use-local" - _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args) + _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args, True) def test_merge_output_strategy_remote_conflict(): @@ -681,7 +683,7 @@ def test_merge_output_strategy_remote_conflict(): expected_conflicts = [] merge_args = copy.deepcopy(args) merge_args.output_strategy = "use-remote" - _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args) + _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args, True) def test_merge_output_strategy_base_conflict(): @@ -693,7 +695,7 @@ def test_merge_output_strategy_base_conflict(): expected_conflicts = [] merge_args = copy.deepcopy(args) merge_args.output_strategy = "use-base" - _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args) + _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args, True) @pytest.mark.skip @@ -706,7 +708,7 @@ def test_merge_output_strategy_union_conflict(): expected_conflicts = [] merge_args = copy.deepcopy(args) merge_args.output_strategy = "union" - _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args) + _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args, True) def test_merge_output_strategy_clear_conflict(): @@ -718,7 +720,7 @@ def test_merge_output_strategy_clear_conflict(): expected_conflicts = [] merge_args = copy.deepcopy(args) merge_args.output_strategy = "remove" - _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args) + _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args, True) def test_merge_output_strategy_clear_all_conflict(): @@ -730,7 +732,7 @@ def test_merge_output_strategy_clear_all_conflict(): expected_conflicts = [] merge_args = copy.deepcopy(args) merge_args.output_strategy = "clear-all" - _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args) + _check_outputs(base, local, remote, expected_partial, expected_conflicts, merge_args, True) # TODO: Make test for output_strategy == 'inline' @@ -852,6 +854,10 @@ def test_autoresolve_empty_strategies(): base, local, remote, expected_partial = _make_notebook_with_multi_conflicts( expected_partial_source, expected_partial_metadata, expected_partial_outputs ) + strip_cell_ids(base) + strip_cell_ids(local) + strip_cell_ids(remote) + strip_cell_ids(expected_partial) expected_conflicts = [ { diff --git a/nbdime/tests/utils.py b/nbdime/tests/utils.py index a97f4661..1b7475f1 100644 --- a/nbdime/tests/utils.py +++ b/nbdime/tests/utils.py @@ -38,6 +38,15 @@ TEST_TOKEN = 'nbdime-test-token' +@contextmanager +def random_seed(a=0): + import random + old_state = random.getstate() + random.seed(a) + yield + random.setstate(old_state) + + def assert_is_valid_notebook(nb): """These are the current assumptions on notebooks in these tests. Loosen on demand.""" assert nb["nbformat"] == 4 @@ -60,16 +69,17 @@ def check_symmetric_diff_and_patch(a, b): check_diff_and_patch(b, a) -def sources_to_notebook(sources, cell_type='code'): +def sources_to_notebook(sources, cell_type='code', id_seed=0): assert isinstance(sources, list) nb = nbformat.v4.new_notebook() - for source in sources: - if isinstance(source, list): - source = "".join(source) - if cell_type == 'code': - nb.cells.append(nbformat.v4.new_code_cell(source)) - elif cell_type == 'markdown': - nb.cells.append(nbformat.v4.new_markdown_cell(source)) + with random_seed(id_seed): + for source in sources: + if isinstance(source, list): + source = "".join(source) + if cell_type == 'code': + nb.cells.append(nbformat.v4.new_code_cell(source)) + elif cell_type == 'markdown': + nb.cells.append(nbformat.v4.new_markdown_cell(source)) return nb @@ -85,7 +95,7 @@ def notebook_to_sources(nb, as_str=True): return sources -def outputs_to_notebook(outputs): +def outputs_to_notebook(outputs, strip_ids=False): assert isinstance(outputs, list) assert len(outputs) == 0 or isinstance(outputs[0], list) nb = nbformat.v4.new_notebook() @@ -102,9 +112,25 @@ def outputs_to_notebook(outputs): ) assert isinstance(output, dict) cell.outputs.append(output) + if strip_ids: + strip_cell_ids(nb) return nb +def strip_cell_ids(nb): + for cell in nb["cells"]: + if "id" in cell: + del cell["id"] + return nb + + +def new_cell_wo_id(*args, **kwargs): + cell = nbformat.v4.new_code_cell(*args, **kwargs) + if "id" in cell: + del cell["id"] + return cell + + @contextmanager def assert_clean_exit(): """Assert that SystemExit is called with code=0"""