X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/b1d060101626aa1c332f52e4bdf0ae5e4cc07990..7aa37ea0adf864baf3ef3dfbcfaf5ff1ff780250:/tests/test_ipynb.py diff --git a/tests/test_ipynb.py b/tests/test_ipynb.py index 038155e..5989719 100644 --- a/tests/test_ipynb.py +++ b/tests/test_ipynb.py @@ -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", "cust2"}), + 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", "cust2"}), + 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", "cust2"}), + "%%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,77 +433,85 @@ 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" - 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 + nb = get_case_path("jupyter", "notebook_trailing_newline.ipynb") + tmp_nb = tmp_path / "notebook.ipynb" + tmp_nb.write_bytes(nb.read_bytes()) + monkeypatch.setattr("black.jupyter_dependencies_are_installed", lambda warn: False) + result = runner.invoke( + main, [str(tmp_path / "notebook.ipynb"), f"--config={EMPTY_CONFIG}"] ) - result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")]) 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 + monkeypatch.setattr("black.jupyter_dependencies_are_installed", lambda warn: True) + result = runner.invoke( + main, [str(tmp_path / "notebook.ipynb"), f"--config={EMPTY_CONFIG}"] ) - result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")]) 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" - with open(nb) as src, open(tmp_nb, "w") as dst: - dst.write(src.read()) + nb = get_case_path("jupyter", "notebook_trailing_newline.ipynb") + tmp_nb = tmp_path / "notebook.ipynb" + tmp_nb.write_bytes(nb.read_bytes()) monkeypatch.setattr( - "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: False + "black.files.jupyter_dependencies_are_installed", lambda warn: 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 + "black.files.jupyter_dependencies_are_installed", lambda warn: 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" - with open(nb) as src, open(tmp_nb, "w") as dst: - dst.write(src.read()) +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" + tmp_nb.write_bytes(nb.read_bytes()) result = runner.invoke( main, [ 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)