]> git.madduck.net Git - etc/vim.git/blobdiff - tests/test_ipynb.py

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Fix an infinite recursion error exposed by #3440 (#3444)
[etc/vim.git] / tests / test_ipynb.py
index 038155e9270e25ec98d3cf35b25329bea3c93eb6..7aa2e91dd0010ba7a8097c677506b21265bf850a 100644 (file)
@@ -1,25 +1,35 @@
+import contextlib
 import pathlib
+import re
+from contextlib import ExitStack as does_not_raise
+from dataclasses import replace
+from typing import ContextManager
+
+import pytest
+from _pytest.monkeypatch import MonkeyPatch
 from click.testing import CliRunner
-from black.handle_ipynb_magics import jupyter_dependencies_are_installed
+
 from black import (
-    main,
+    Mode,
     NothingChanged,
     format_cell,
     format_file_contents,
     format_file_in_place,
+    main,
 )
-import os
-import pytest
-from black import Mode
-from _pytest.monkeypatch import MonkeyPatch
-from py.path import local
+from black.handle_ipynb_magics import jupyter_dependencies_are_installed
+from tests.util import DATA_DIR, get_case_path, read_jupyter_notebook
 
+with contextlib.suppress(ModuleNotFoundError):
+    import IPython
 pytestmark = pytest.mark.jupyter
 pytest.importorskip("IPython", reason="IPython is an optional dependency")
 pytest.importorskip("tokenize_rt", reason="tokenize-rt is an optional dependency")
 
 JUPYTER_MODE = Mode(is_ipynb=True)
 
+EMPTY_CONFIG = DATA_DIR / "empty_pyproject.toml"
+
 runner = CliRunner()
 
 
@@ -62,9 +72,19 @@ def test_trailing_semicolon_noop() -> None:
         format_cell(src, fast=True, mode=JUPYTER_MODE)
 
 
-def test_cell_magic() -> None:
+@pytest.mark.parametrize(
+    "mode",
+    [
+        pytest.param(JUPYTER_MODE, id="default mode"),
+        pytest.param(
+            replace(JUPYTER_MODE, python_cell_magics={"cust1", "cust1"}),
+            id="custom cell magics mode",
+        ),
+    ],
+)
+def test_cell_magic(mode: Mode) -> None:
     src = "%%time\nfoo =bar"
-    result = format_cell(src, fast=True, mode=JUPYTER_MODE)
+    result = format_cell(src, fast=True, mode=mode)
     expected = "%%time\nfoo = bar"
     assert result == expected
 
@@ -75,6 +95,16 @@ def test_cell_magic_noop() -> None:
         format_cell(src, fast=True, mode=JUPYTER_MODE)
 
 
+@pytest.mark.parametrize(
+    "mode",
+    [
+        pytest.param(JUPYTER_MODE, id="default mode"),
+        pytest.param(
+            replace(JUPYTER_MODE, python_cell_magics={"cust1", "cust1"}),
+            id="custom cell magics mode",
+        ),
+    ],
+)
 @pytest.mark.parametrize(
     "src, expected",
     (
@@ -89,10 +119,14 @@ def test_cell_magic_noop() -> None:
             id="Line magic with argument",
         ),
         pytest.param("%time\n'foo'", '%time\n"foo"', id="Line magic without argument"),
+        pytest.param(
+            "env =  %env var", "env = %env var", id="Assignment to environment variable"
+        ),
+        pytest.param("env =  %env", "env = %env", id="Assignment to magic"),
     ),
 )
-def test_magic(src: str, expected: str) -> None:
-    result = format_cell(src, fast=True, mode=JUPYTER_MODE)
+def test_magic(src: str, expected: str, mode: Mode) -> None:
+    result = format_cell(src, fast=True, mode=mode)
     assert result == expected
 
 
@@ -101,6 +135,7 @@ def test_magic(src: str, expected: str) -> None:
     (
         "%%bash\n2+2",
         "%%html --isolated\n2+2",
+        "%%writefile e.txt\n  meh\n meh",
     ),
 )
 def test_non_python_magics(src: str) -> None:
@@ -108,10 +143,15 @@ def test_non_python_magics(src: str) -> None:
         format_cell(src, fast=True, mode=JUPYTER_MODE)
 
 
+@pytest.mark.skipif(
+    IPython.version_info < (8, 3),
+    reason="Change in how TransformerManager transforms this input",
+)
 def test_set_input() -> None:
     src = "a = b??"
-    with pytest.raises(NothingChanged):
-        format_cell(src, fast=True, mode=JUPYTER_MODE)
+    expected = "??b"
+    result = format_cell(src, fast=True, mode=JUPYTER_MODE)
+    assert result == expected
 
 
 def test_input_already_contains_transformed_magic() -> None:
@@ -127,12 +167,47 @@ def test_magic_noop() -> None:
 
 
 def test_cell_magic_with_magic() -> None:
-    src = "%%t -n1\nls =!ls"
+    src = "%%timeit -n1\nls =!ls"
     result = format_cell(src, fast=True, mode=JUPYTER_MODE)
-    expected = "%%t -n1\nls = !ls"
+    expected = "%%timeit -n1\nls = !ls"
     assert result == expected
 
 
+@pytest.mark.parametrize(
+    "mode, expected_output, expectation",
+    [
+        pytest.param(
+            JUPYTER_MODE,
+            "%%custom_python_magic -n1 -n2\nx=2",
+            pytest.raises(NothingChanged),
+            id="No change when cell magic not registered",
+        ),
+        pytest.param(
+            replace(JUPYTER_MODE, python_cell_magics={"cust1", "cust1"}),
+            "%%custom_python_magic -n1 -n2\nx=2",
+            pytest.raises(NothingChanged),
+            id="No change when other cell magics registered",
+        ),
+        pytest.param(
+            replace(JUPYTER_MODE, python_cell_magics={"custom_python_magic", "cust1"}),
+            "%%custom_python_magic -n1 -n2\nx = 2",
+            does_not_raise(),
+            id="Correctly change when cell magic registered",
+        ),
+    ],
+)
+def test_cell_magic_with_custom_python_magic(
+    mode: Mode, expected_output: str, expectation: ContextManager[object]
+) -> None:
+    with expectation:
+        result = format_cell(
+            "%%custom_python_magic -n1 -n2\nx=2",
+            fast=True,
+            mode=mode,
+        )
+        assert result == expected_output
+
+
 def test_cell_magic_nested() -> None:
     src = "%%time\n%%time\n2+2"
     result = format_cell(src, fast=True, mode=JUPYTER_MODE)
@@ -178,11 +253,7 @@ def test_empty_cell() -> None:
 
 
 def test_entire_notebook_empty_metadata() -> None:
-    with open(
-        os.path.join("tests", "data", "notebook_empty_metadata.ipynb"), "rb"
-    ) as fd:
-        content_bytes = fd.read()
-    content = content_bytes.decode()
+    content = read_jupyter_notebook("jupyter", "notebook_empty_metadata")
     result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
     expected = (
         "{\n"
@@ -217,11 +288,7 @@ def test_entire_notebook_empty_metadata() -> None:
 
 
 def test_entire_notebook_trailing_newline() -> None:
-    with open(
-        os.path.join("tests", "data", "notebook_trailing_newline.ipynb"), "rb"
-    ) as fd:
-        content_bytes = fd.read()
-    content = content_bytes.decode()
+    content = read_jupyter_notebook("jupyter", "notebook_trailing_newline")
     result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
     expected = (
         "{\n"
@@ -268,11 +335,7 @@ def test_entire_notebook_trailing_newline() -> None:
 
 
 def test_entire_notebook_no_trailing_newline() -> None:
-    with open(
-        os.path.join("tests", "data", "notebook_no_trailing_newline.ipynb"), "rb"
-    ) as fd:
-        content_bytes = fd.read()
-    content = content_bytes.decode()
+    content = read_jupyter_notebook("jupyter", "notebook_no_trailing_newline")
     result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
     expected = (
         "{\n"
@@ -319,19 +382,14 @@ def test_entire_notebook_no_trailing_newline() -> None:
 
 
 def test_entire_notebook_without_changes() -> None:
-    with open(
-        os.path.join("tests", "data", "notebook_without_changes.ipynb"), "rb"
-    ) as fd:
-        content_bytes = fd.read()
-    content = content_bytes.decode()
+    content = read_jupyter_notebook("jupyter", "notebook_without_changes")
     with pytest.raises(NothingChanged):
         format_file_contents(content, fast=True, mode=JUPYTER_MODE)
 
 
 def test_non_python_notebook() -> None:
-    with open(os.path.join("tests", "data", "non_python_notebook.ipynb"), "rb") as fd:
-        content_bytes = fd.read()
-    content = content_bytes.decode()
+    content = read_jupyter_notebook("jupyter", "non_python_notebook")
+
     with pytest.raises(NothingChanged):
         format_file_contents(content, fast=True, mode=JUPYTER_MODE)
 
@@ -342,27 +400,22 @@ def test_empty_string() -> None:
 
 
 def test_unparseable_notebook() -> None:
-    msg = (
-        r"File 'tests[/\\]data[/\\]notebook_which_cant_be_parsed\.ipynb' "
-        r"cannot be parsed as valid Jupyter notebook\."
-    )
+    path = get_case_path("jupyter", "notebook_which_cant_be_parsed.ipynb")
+    msg = rf"File '{re.escape(str(path))}' cannot be parsed as valid Jupyter notebook\."
     with pytest.raises(ValueError, match=msg):
-        format_file_in_place(
-            pathlib.Path("tests") / "data/notebook_which_cant_be_parsed.ipynb",
-            fast=True,
-            mode=JUPYTER_MODE,
-        )
+        format_file_in_place(path, fast=True, mode=JUPYTER_MODE)
 
 
 def test_ipynb_diff_with_change() -> None:
     result = runner.invoke(
         main,
         [
-            os.path.join("tests", "data", "notebook_trailing_newline.ipynb"),
+            str(get_case_path("jupyter", "notebook_trailing_newline.ipynb")),
             "--diff",
+            f"--config={EMPTY_CONFIG}",
         ],
     )
-    expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
+    expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n+print(\"foo\")\n"
     assert expected in result.output
 
 
@@ -370,8 +423,9 @@ def test_ipynb_diff_with_no_change() -> None:
     result = runner.invoke(
         main,
         [
-            os.path.join("tests", "data", "notebook_without_changes.ipynb"),
+            str(get_case_path("jupyter", "notebook_without_changes.ipynb")),
             "--diff",
+            f"--config={EMPTY_CONFIG}",
         ],
     )
     expected = "1 file would be left unchanged."
@@ -379,52 +433,56 @@ def test_ipynb_diff_with_no_change() -> None:
 
 
 def test_cache_isnt_written_if_no_jupyter_deps_single(
-    monkeypatch: MonkeyPatch, tmpdir: local
+    monkeypatch: MonkeyPatch, tmp_path: pathlib.Path
 ) -> None:
     # Check that the cache isn't written to if Jupyter dependencies aren't installed.
     jupyter_dependencies_are_installed.cache_clear()
-    nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
-    tmp_nb = tmpdir / "notebook.ipynb"
+    nb = get_case_path("jupyter", "notebook_trailing_newline.ipynb")
+    tmp_nb = tmp_path / "notebook.ipynb"
     with open(nb) as src, open(tmp_nb, "w") as dst:
         dst.write(src.read())
     monkeypatch.setattr(
         "black.jupyter_dependencies_are_installed", lambda verbose, quiet: False
     )
-    result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
+    result = runner.invoke(
+        main, [str(tmp_path / "notebook.ipynb"), f"--config={EMPTY_CONFIG}"]
+    )
     assert "No Python files are present to be formatted. Nothing to do" in result.output
     jupyter_dependencies_are_installed.cache_clear()
     monkeypatch.setattr(
         "black.jupyter_dependencies_are_installed", lambda verbose, quiet: True
     )
-    result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
+    result = runner.invoke(
+        main, [str(tmp_path / "notebook.ipynb"), f"--config={EMPTY_CONFIG}"]
+    )
     assert "reformatted" in result.output
 
 
 def test_cache_isnt_written_if_no_jupyter_deps_dir(
-    monkeypatch: MonkeyPatch, tmpdir: local
+    monkeypatch: MonkeyPatch, tmp_path: pathlib.Path
 ) -> None:
     # Check that the cache isn't written to if Jupyter dependencies aren't installed.
     jupyter_dependencies_are_installed.cache_clear()
-    nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
-    tmp_nb = tmpdir / "notebook.ipynb"
+    nb = get_case_path("jupyter", "notebook_trailing_newline.ipynb")
+    tmp_nb = tmp_path / "notebook.ipynb"
     with open(nb) as src, open(tmp_nb, "w") as dst:
         dst.write(src.read())
     monkeypatch.setattr(
         "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: False
     )
-    result = runner.invoke(main, [str(tmpdir)])
+    result = runner.invoke(main, [str(tmp_path), f"--config={EMPTY_CONFIG}"])
     assert "No Python files are present to be formatted. Nothing to do" in result.output
     jupyter_dependencies_are_installed.cache_clear()
     monkeypatch.setattr(
         "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: True
     )
-    result = runner.invoke(main, [str(tmpdir)])
+    result = runner.invoke(main, [str(tmp_path), f"--config={EMPTY_CONFIG}"])
     assert "reformatted" in result.output
 
 
-def test_ipynb_flag(tmpdir: local) -> None:
-    nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
-    tmp_nb = tmpdir / "notebook.a_file_extension_which_is_definitely_not_ipynb"
+def test_ipynb_flag(tmp_path: pathlib.Path) -> None:
+    nb = get_case_path("jupyter", "notebook_trailing_newline.ipynb")
+    tmp_nb = tmp_path / "notebook.a_file_extension_which_is_definitely_not_ipynb"
     with open(nb) as src, open(tmp_nb, "w") as dst:
         dst.write(src.read())
     result = runner.invoke(
@@ -433,23 +491,34 @@ def test_ipynb_flag(tmpdir: local) -> None:
             str(tmp_nb),
             "--diff",
             "--ipynb",
+            f"--config={EMPTY_CONFIG}",
         ],
     )
-    expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
+    expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n+print(\"foo\")\n"
     assert expected in result.output
 
 
 def test_ipynb_and_pyi_flags() -> None:
-    nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
+    nb = get_case_path("jupyter", "notebook_trailing_newline.ipynb")
     result = runner.invoke(
         main,
         [
-            nb,
+            str(nb),
             "--pyi",
             "--ipynb",
             "--diff",
+            f"--config={EMPTY_CONFIG}",
         ],
     )
     assert isinstance(result.exception, SystemExit)
     expected = "Cannot pass both `pyi` and `ipynb` flags!\n"
     assert result.output == expected
+
+
+def test_unable_to_replace_magics(monkeypatch: MonkeyPatch) -> None:
+    src = "%%time\na = 'foo'"
+    monkeypatch.setattr("black.handle_ipynb_magics.TOKEN_HEX", lambda _: "foo")
+    with pytest.raises(
+        AssertionError, match="Black was not able to replace IPython magic"
+    ):
+        format_cell(src, fast=True, mode=JUPYTER_MODE)