Files

186 lines
6.8 KiB
Python

import contextlib
import io
import unittest
from pathlib import Path
from unittest.mock import patch
from IPython.core.history import HistoryOutput
from IPython.core.interactiveshell import InteractiveShell
from IPython.utils import capture
import ipython_shell.events as events_module
import ipython_shell.models as models_module
import ipython_shell.shell as shell_module
PACKAGE_ROOT = Path(__file__).parents[1] / "src" / "ipython_demo"
class ShellTests(unittest.TestCase):
def test_event_from_history_output_preserves_streams_and_mime(self):
stdout = events_module.event_from_history_output(
HistoryOutput("out_stream", {"stream": ["hello", "\n"]}),
call_id="call-1",
sequence=3,
)
result = events_module.event_from_history_output(
HistoryOutput("execute_result", {"text/plain": "42"}),
call_id="call-1",
sequence=4,
)
self.assertEqual(
stdout,
models_module.CallEvent(
call_id="call-1", sequence=3, kind="stdout", text="hello\n"
),
)
self.assertEqual(result.data, {"text/plain": "42"})
def test_run_cell_and_collect_returns_execution_and_events(self):
shell = InteractiveShell()
shell_module.setup_shell(shell, plt=False, pylab=False)
with contextlib.redirect_stdout(io.StringIO()):
execution, events = shell_module.run_cell_and_collect(
shell,
"print('hello'); 42",
call_id="call-1",
)
self.assertIsInstance(execution, models_module.CallResult)
self.assertEqual(execution.result, 42)
self.assertEqual([event.kind for event in events], ["stdout", "execute_result"])
self.assertEqual(events[0].text, "hello\n")
self.assertEqual(events[1].data, {"text/plain": "42"})
def test_run_cell_and_collect_converts_execution_error(self):
shell = InteractiveShell()
shell_module.setup_shell(shell, plt=False, pylab=False)
with contextlib.redirect_stdout(io.StringIO()):
execution, events = shell_module.run_cell_and_collect(
shell,
"raise ValueError('boom')",
call_id="call-2",
)
self.assertIsInstance(execution.error, models_module.CallError)
assert execution.error
self.assertEqual(execution.error.ename, "ValueError")
self.assertEqual(execution.error.evalue, "boom")
self.assertTrue(events, "no events")
self.assertEqual(events[-1].kind, "error")
self.assertTrue(events[-1].data, "no data in last event")
assert events[-1].data
self.assertEqual(events[-1].data["ename"], "ValueError")
self.assertEqual(events[-1].data["evalue"], "boom")
def test_setup_shell_isolates_history_output_storage(self):
first = InteractiveShell()
second = InteractiveShell()
shell_module.setup_shell(first, plt=False, pylab=False)
shell_module.setup_shell(second, plt=False, pylab=False)
self.assertIsNot(first.history_manager.outputs, second.history_manager.outputs)
def test_setup_shell_configures_mime_output_without_enable_gui(self):
shell = InteractiveShell()
def fail_enable_gui(*args, **kwargs):
raise AssertionError("base InteractiveShell must not require enable_gui")
with patch.object(InteractiveShell, "enable_gui", fail_enable_gui):
shell_module.setup_shell(shell, plt=True, pylab=False)
with contextlib.redirect_stdout(io.StringIO()):
result = shell.run_cell("2 + 2", store_history=True)
self.assertEqual(result.result, 4)
records = shell.history_manager.outputs[result.execution_count]
self.assertEqual([record.output_type for record in records], ["execute_result"])
self.assertEqual(records[0].bundle["text/plain"], "4")
def test_setup_shell_keeps_pylab_namespace_setup_without_enable_gui(self):
shell = InteractiveShell()
def fail_enable_gui(*args, **kwargs):
raise AssertionError("pylab setup must not require enable_gui")
with patch.object(InteractiveShell, "enable_gui", fail_enable_gui):
shell_module.setup_shell(shell, plt=True, pylab=True)
self.assertIn("plt", shell.user_ns)
self.assertIn("np", shell.user_ns)
self.assertIs(shell.user_ns["plt"], shell.user_ns_hidden["plt"])
self.assertIs(shell.user_ns["np"], shell.user_ns_hidden["np"])
def test_setup_shell_installs_matplotlib_runner_without_enable_gui(self):
shell = InteractiveShell()
shell_module.setup_shell(shell, plt=True, pylab=False)
self.assertIsNotNone(
shell.magics_manager.registry["ExecutionMagics"].default_runner
)
def test_carefully_setup_shell_preserves_formats_and_is_idempotent(self):
from matplotlib.figure import Figure
from matplotlib_inline.backend_inline import flush_figures
shell = InteractiveShell()
shell_module.carefully_setup_shell(shell, formats={"svg"})
shell_module.carefully_setup_shell(shell, formats={"png"})
callbacks = shell.events.callbacks["post_execute"]
self.assertEqual(callbacks.count(flush_figures), 1)
self.assertIn(
Figure,
shell.display_formatter.formatters["image/svg+xml"].type_printers,
)
self.assertIn(
Figure,
shell.display_formatter.formatters["image/png"].type_printers,
)
def test_enable_pylab_lite_reports_clobbered_names(self):
shell = InteractiveShell()
original_np = object()
shell.user_ns["np"] = original_np
clobbered = shell_module.enable_pylab_lite(shell, import_all=False)
self.assertIn("np", clobbered)
self.assertIsNot(shell.user_ns["np"], original_np)
self.assertIs(shell.user_ns["np"], shell.user_ns_hidden["np"])
def test_run_cell_does_not_use_global_capture_output(self):
wrapper = shell_module.Shell("test")
wrapper.init_shell()
def fail_capture_output(*args, **kwargs):
raise AssertionError(
"Shell.run_cell must use the shell-local output records"
)
with (
patch.object(
capture,
"capture_output",
fail_capture_output,
),
contextlib.redirect_stdout(io.StringIO()),
):
result, events = wrapper.run_cell(
"print('hello'); 42",
call_id="call-1",
)
self.assertEqual(result.result, 42)
self.assertEqual([event.kind for event in events], ["stdout", "execute_result"])
self.assertEqual(events[0].text, "hello\n")
if __name__ == "__main__":
unittest.main()