]> git.madduck.net Git - etc/vim.git/blob - 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:

DOC: cleanup pre-commit instructions following #2430 (#2481)
[etc/vim.git] / tests / test_ipynb.py
1 import pathlib
2 from click.testing import CliRunner
3 from black.handle_ipynb_magics import jupyter_dependencies_are_installed
4 from black import (
5     main,
6     NothingChanged,
7     format_cell,
8     format_file_contents,
9     format_file_in_place,
10 )
11 import os
12 import pytest
13 from black import Mode
14 from _pytest.monkeypatch import MonkeyPatch
15 from py.path import local
16
17 pytestmark = pytest.mark.jupyter
18 pytest.importorskip("IPython", reason="IPython is an optional dependency")
19 pytest.importorskip("tokenize_rt", reason="tokenize-rt is an optional dependency")
20
21 JUPYTER_MODE = Mode(is_ipynb=True)
22
23 runner = CliRunner()
24
25
26 def test_noop() -> None:
27     src = 'foo = "a"'
28     with pytest.raises(NothingChanged):
29         format_cell(src, fast=True, mode=JUPYTER_MODE)
30
31
32 @pytest.mark.parametrize("fast", [True, False])
33 def test_trailing_semicolon(fast: bool) -> None:
34     src = 'foo = "a" ;'
35     result = format_cell(src, fast=fast, mode=JUPYTER_MODE)
36     expected = 'foo = "a";'
37     assert result == expected
38
39
40 def test_trailing_semicolon_with_comment() -> None:
41     src = 'foo = "a" ;  # bar'
42     result = format_cell(src, fast=True, mode=JUPYTER_MODE)
43     expected = 'foo = "a";  # bar'
44     assert result == expected
45
46
47 def test_trailing_semicolon_with_comment_on_next_line() -> None:
48     src = "import black;\n\n# this is a comment"
49     with pytest.raises(NothingChanged):
50         format_cell(src, fast=True, mode=JUPYTER_MODE)
51
52
53 def test_trailing_semicolon_indented() -> None:
54     src = "with foo:\n    plot_bar();"
55     with pytest.raises(NothingChanged):
56         format_cell(src, fast=True, mode=JUPYTER_MODE)
57
58
59 def test_trailing_semicolon_noop() -> None:
60     src = 'foo = "a";'
61     with pytest.raises(NothingChanged):
62         format_cell(src, fast=True, mode=JUPYTER_MODE)
63
64
65 def test_cell_magic() -> None:
66     src = "%%time\nfoo =bar"
67     result = format_cell(src, fast=True, mode=JUPYTER_MODE)
68     expected = "%%time\nfoo = bar"
69     assert result == expected
70
71
72 def test_cell_magic_noop() -> None:
73     src = "%%time\n2 + 2"
74     with pytest.raises(NothingChanged):
75         format_cell(src, fast=True, mode=JUPYTER_MODE)
76
77
78 @pytest.mark.parametrize(
79     "src, expected",
80     (
81         pytest.param("ls =!ls", "ls = !ls", id="System assignment"),
82         pytest.param("!ls\n'foo'", '!ls\n"foo"', id="System call"),
83         pytest.param("!!ls\n'foo'", '!!ls\n"foo"', id="Other system call"),
84         pytest.param("?str\n'foo'", '?str\n"foo"', id="Help"),
85         pytest.param("??str\n'foo'", '??str\n"foo"', id="Other help"),
86         pytest.param(
87             "%matplotlib inline\n'foo'",
88             '%matplotlib inline\n"foo"',
89             id="Line magic with argument",
90         ),
91         pytest.param("%time\n'foo'", '%time\n"foo"', id="Line magic without argument"),
92     ),
93 )
94 def test_magic(src: str, expected: str) -> None:
95     result = format_cell(src, fast=True, mode=JUPYTER_MODE)
96     assert result == expected
97
98
99 @pytest.mark.parametrize(
100     "src",
101     (
102         "%%bash\n2+2",
103         "%%html --isolated\n2+2",
104     ),
105 )
106 def test_non_python_magics(src: str) -> None:
107     with pytest.raises(NothingChanged):
108         format_cell(src, fast=True, mode=JUPYTER_MODE)
109
110
111 def test_set_input() -> None:
112     src = "a = b??"
113     with pytest.raises(NothingChanged):
114         format_cell(src, fast=True, mode=JUPYTER_MODE)
115
116
117 def test_input_already_contains_transformed_magic() -> None:
118     src = '%time foo()\nget_ipython().run_cell_magic("time", "", "foo()\\n")'
119     with pytest.raises(NothingChanged):
120         format_cell(src, fast=True, mode=JUPYTER_MODE)
121
122
123 def test_magic_noop() -> None:
124     src = "ls = !ls"
125     with pytest.raises(NothingChanged):
126         format_cell(src, fast=True, mode=JUPYTER_MODE)
127
128
129 def test_cell_magic_with_magic() -> None:
130     src = "%%t -n1\nls =!ls"
131     result = format_cell(src, fast=True, mode=JUPYTER_MODE)
132     expected = "%%t -n1\nls = !ls"
133     assert result == expected
134
135
136 def test_cell_magic_nested() -> None:
137     src = "%%time\n%%time\n2+2"
138     result = format_cell(src, fast=True, mode=JUPYTER_MODE)
139     expected = "%%time\n%%time\n2 + 2"
140     assert result == expected
141
142
143 def test_cell_magic_with_magic_noop() -> None:
144     src = "%%t -n1\nls = !ls"
145     with pytest.raises(NothingChanged):
146         format_cell(src, fast=True, mode=JUPYTER_MODE)
147
148
149 def test_automagic() -> None:
150     src = "pip install black"
151     with pytest.raises(NothingChanged):
152         format_cell(src, fast=True, mode=JUPYTER_MODE)
153
154
155 def test_multiline_magic() -> None:
156     src = "%time 1 + \\\n2"
157     with pytest.raises(NothingChanged):
158         format_cell(src, fast=True, mode=JUPYTER_MODE)
159
160
161 def test_multiline_no_magic() -> None:
162     src = "1 + \\\n2"
163     result = format_cell(src, fast=True, mode=JUPYTER_MODE)
164     expected = "1 + 2"
165     assert result == expected
166
167
168 def test_cell_magic_with_invalid_body() -> None:
169     src = "%%time\nif True"
170     with pytest.raises(NothingChanged):
171         format_cell(src, fast=True, mode=JUPYTER_MODE)
172
173
174 def test_empty_cell() -> None:
175     src = ""
176     with pytest.raises(NothingChanged):
177         format_cell(src, fast=True, mode=JUPYTER_MODE)
178
179
180 def test_entire_notebook_empty_metadata() -> None:
181     with open(
182         os.path.join("tests", "data", "notebook_empty_metadata.ipynb"), "rb"
183     ) as fd:
184         content_bytes = fd.read()
185     content = content_bytes.decode()
186     result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
187     expected = (
188         "{\n"
189         ' "cells": [\n'
190         "  {\n"
191         '   "cell_type": "code",\n'
192         '   "execution_count": null,\n'
193         '   "metadata": {\n'
194         '    "tags": []\n'
195         "   },\n"
196         '   "outputs": [],\n'
197         '   "source": [\n'
198         '    "%%time\\n",\n'
199         '    "\\n",\n'
200         '    "print(\\"foo\\")"\n'
201         "   ]\n"
202         "  },\n"
203         "  {\n"
204         '   "cell_type": "code",\n'
205         '   "execution_count": null,\n'
206         '   "metadata": {},\n'
207         '   "outputs": [],\n'
208         '   "source": []\n'
209         "  }\n"
210         " ],\n"
211         ' "metadata": {},\n'
212         ' "nbformat": 4,\n'
213         ' "nbformat_minor": 4\n'
214         "}\n"
215     )
216     assert result == expected
217
218
219 def test_entire_notebook_trailing_newline() -> None:
220     with open(
221         os.path.join("tests", "data", "notebook_trailing_newline.ipynb"), "rb"
222     ) as fd:
223         content_bytes = fd.read()
224     content = content_bytes.decode()
225     result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
226     expected = (
227         "{\n"
228         ' "cells": [\n'
229         "  {\n"
230         '   "cell_type": "code",\n'
231         '   "execution_count": null,\n'
232         '   "metadata": {\n'
233         '    "tags": []\n'
234         "   },\n"
235         '   "outputs": [],\n'
236         '   "source": [\n'
237         '    "%%time\\n",\n'
238         '    "\\n",\n'
239         '    "print(\\"foo\\")"\n'
240         "   ]\n"
241         "  },\n"
242         "  {\n"
243         '   "cell_type": "code",\n'
244         '   "execution_count": null,\n'
245         '   "metadata": {},\n'
246         '   "outputs": [],\n'
247         '   "source": []\n'
248         "  }\n"
249         " ],\n"
250         ' "metadata": {\n'
251         '  "interpreter": {\n'
252         '   "hash": "e758f3098b5b55f4d87fe30bbdc1367f20f246b483f96267ee70e6c40cb185d8"\n'  # noqa:B950
253         "  },\n"
254         '  "kernelspec": {\n'
255         '   "display_name": "Python 3.8.10 64-bit (\'black\': venv)",\n'
256         '   "name": "python3"\n'
257         "  },\n"
258         '  "language_info": {\n'
259         '   "name": "python",\n'
260         '   "version": ""\n'
261         "  }\n"
262         " },\n"
263         ' "nbformat": 4,\n'
264         ' "nbformat_minor": 4\n'
265         "}\n"
266     )
267     assert result == expected
268
269
270 def test_entire_notebook_no_trailing_newline() -> None:
271     with open(
272         os.path.join("tests", "data", "notebook_no_trailing_newline.ipynb"), "rb"
273     ) as fd:
274         content_bytes = fd.read()
275     content = content_bytes.decode()
276     result = format_file_contents(content, fast=True, mode=JUPYTER_MODE)
277     expected = (
278         "{\n"
279         ' "cells": [\n'
280         "  {\n"
281         '   "cell_type": "code",\n'
282         '   "execution_count": null,\n'
283         '   "metadata": {\n'
284         '    "tags": []\n'
285         "   },\n"
286         '   "outputs": [],\n'
287         '   "source": [\n'
288         '    "%%time\\n",\n'
289         '    "\\n",\n'
290         '    "print(\\"foo\\")"\n'
291         "   ]\n"
292         "  },\n"
293         "  {\n"
294         '   "cell_type": "code",\n'
295         '   "execution_count": null,\n'
296         '   "metadata": {},\n'
297         '   "outputs": [],\n'
298         '   "source": []\n'
299         "  }\n"
300         " ],\n"
301         ' "metadata": {\n'
302         '  "interpreter": {\n'
303         '   "hash": "e758f3098b5b55f4d87fe30bbdc1367f20f246b483f96267ee70e6c40cb185d8"\n'  # noqa: B950
304         "  },\n"
305         '  "kernelspec": {\n'
306         '   "display_name": "Python 3.8.10 64-bit (\'black\': venv)",\n'
307         '   "name": "python3"\n'
308         "  },\n"
309         '  "language_info": {\n'
310         '   "name": "python",\n'
311         '   "version": ""\n'
312         "  }\n"
313         " },\n"
314         ' "nbformat": 4,\n'
315         ' "nbformat_minor": 4\n'
316         "}"
317     )
318     assert result == expected
319
320
321 def test_entire_notebook_without_changes() -> None:
322     with open(
323         os.path.join("tests", "data", "notebook_without_changes.ipynb"), "rb"
324     ) as fd:
325         content_bytes = fd.read()
326     content = content_bytes.decode()
327     with pytest.raises(NothingChanged):
328         format_file_contents(content, fast=True, mode=JUPYTER_MODE)
329
330
331 def test_non_python_notebook() -> None:
332     with open(os.path.join("tests", "data", "non_python_notebook.ipynb"), "rb") as fd:
333         content_bytes = fd.read()
334     content = content_bytes.decode()
335     with pytest.raises(NothingChanged):
336         format_file_contents(content, fast=True, mode=JUPYTER_MODE)
337
338
339 def test_empty_string() -> None:
340     with pytest.raises(NothingChanged):
341         format_file_contents("", fast=True, mode=JUPYTER_MODE)
342
343
344 def test_unparseable_notebook() -> None:
345     msg = (
346         r"File 'tests[/\\]data[/\\]notebook_which_cant_be_parsed\.ipynb' "
347         r"cannot be parsed as valid Jupyter notebook\."
348     )
349     with pytest.raises(ValueError, match=msg):
350         format_file_in_place(
351             pathlib.Path("tests") / "data/notebook_which_cant_be_parsed.ipynb",
352             fast=True,
353             mode=JUPYTER_MODE,
354         )
355
356
357 def test_ipynb_diff_with_change() -> None:
358     result = runner.invoke(
359         main,
360         [
361             os.path.join("tests", "data", "notebook_trailing_newline.ipynb"),
362             "--diff",
363         ],
364     )
365     expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
366     assert expected in result.output
367
368
369 def test_ipynb_diff_with_no_change() -> None:
370     result = runner.invoke(
371         main,
372         [
373             os.path.join("tests", "data", "notebook_without_changes.ipynb"),
374             "--diff",
375         ],
376     )
377     expected = "1 file would be left unchanged."
378     assert expected in result.output
379
380
381 def test_cache_isnt_written_if_no_jupyter_deps_single(
382     monkeypatch: MonkeyPatch, tmpdir: local
383 ) -> None:
384     # Check that the cache isn't written to if Jupyter dependencies aren't installed.
385     jupyter_dependencies_are_installed.cache_clear()
386     nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
387     tmp_nb = tmpdir / "notebook.ipynb"
388     with open(nb) as src, open(tmp_nb, "w") as dst:
389         dst.write(src.read())
390     monkeypatch.setattr(
391         "black.jupyter_dependencies_are_installed", lambda verbose, quiet: False
392     )
393     result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
394     assert "No Python files are present to be formatted. Nothing to do" in result.output
395     jupyter_dependencies_are_installed.cache_clear()
396     monkeypatch.setattr(
397         "black.jupyter_dependencies_are_installed", lambda verbose, quiet: True
398     )
399     result = runner.invoke(main, [str(tmpdir / "notebook.ipynb")])
400     assert "reformatted" in result.output
401
402
403 def test_cache_isnt_written_if_no_jupyter_deps_dir(
404     monkeypatch: MonkeyPatch, tmpdir: local
405 ) -> None:
406     # Check that the cache isn't written to if Jupyter dependencies aren't installed.
407     jupyter_dependencies_are_installed.cache_clear()
408     nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
409     tmp_nb = tmpdir / "notebook.ipynb"
410     with open(nb) as src, open(tmp_nb, "w") as dst:
411         dst.write(src.read())
412     monkeypatch.setattr(
413         "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: False
414     )
415     result = runner.invoke(main, [str(tmpdir)])
416     assert "No Python files are present to be formatted. Nothing to do" in result.output
417     jupyter_dependencies_are_installed.cache_clear()
418     monkeypatch.setattr(
419         "black.files.jupyter_dependencies_are_installed", lambda verbose, quiet: True
420     )
421     result = runner.invoke(main, [str(tmpdir)])
422     assert "reformatted" in result.output
423
424
425 def test_ipynb_flag(tmpdir: local) -> None:
426     nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
427     tmp_nb = tmpdir / "notebook.a_file_extension_which_is_definitely_not_ipynb"
428     with open(nb) as src, open(tmp_nb, "w") as dst:
429         dst.write(src.read())
430     result = runner.invoke(
431         main,
432         [
433             str(tmp_nb),
434             "--diff",
435             "--ipynb",
436         ],
437     )
438     expected = "@@ -1,3 +1,3 @@\n %%time\n \n-print('foo')\n" '+print("foo")\n'
439     assert expected in result.output
440
441
442 def test_ipynb_and_pyi_flags() -> None:
443     nb = os.path.join("tests", "data", "notebook_trailing_newline.ipynb")
444     result = runner.invoke(
445         main,
446         [
447             nb,
448             "--pyi",
449             "--ipynb",
450             "--diff",
451         ],
452     )
453     assert isinstance(result.exception, SystemExit)
454     expected = "Cannot pass both `pyi` and `ipynb` flags!\n"
455     assert result.output == expected