|
1 | 1 | import contextlib |
2 | 2 | import io |
| 3 | +import sys |
3 | 4 | import warnings |
4 | 5 | import unittest |
5 | | -from unittest.mock import patch |
| 6 | +from unittest.mock import patch, MagicMock |
6 | 7 | from textwrap import dedent |
7 | 8 |
|
8 | 9 | from test.support import force_not_colorized |
@@ -299,3 +300,53 @@ def f(): |
299 | 300 | count = sum("'return' in a 'finally' block" in str(w.message) |
300 | 301 | for w in caught) |
301 | 302 | self.assertEqual(count, 1) |
| 303 | + |
| 304 | + |
| 305 | +class TestPreexecHook(unittest.TestCase): |
| 306 | + |
| 307 | + def _run_interactive(self, statements, *, preexec=None): |
| 308 | + from _pyrepl.simple_interact import run_multiline_interactive_console |
| 309 | + |
| 310 | + console = InteractiveColoredConsole() |
| 311 | + statement_iter = iter(statements) |
| 312 | + |
| 313 | + def fake_multiline_input(more_lines, ps1, ps2): |
| 314 | + try: |
| 315 | + return next(statement_iter) |
| 316 | + except StopIteration: |
| 317 | + raise EOFError |
| 318 | + |
| 319 | + patches = [ |
| 320 | + patch("_pyrepl.simple_interact.multiline_input", side_effect=fake_multiline_input), |
| 321 | + patch("_pyrepl.simple_interact._get_reader"), |
| 322 | + patch("_pyrepl.simple_interact.append_history_file"), |
| 323 | + patch("_pyrepl.readline._setup"), |
| 324 | + ] |
| 325 | + if preexec is not None: |
| 326 | + patches.append(patch.object(sys, "_preexec", preexec, create=True)) |
| 327 | + |
| 328 | + f = io.StringIO() |
| 329 | + with contextlib.ExitStack() as stack: |
| 330 | + for p in patches: |
| 331 | + stack.enter_context(p) |
| 332 | + stack.enter_context(contextlib.redirect_stdout(f)) |
| 333 | + stack.enter_context(contextlib.redirect_stderr(f)) |
| 334 | + run_multiline_interactive_console(console) |
| 335 | + |
| 336 | + return f.getvalue() |
| 337 | + |
| 338 | + def test_preexec_called_with_statement(self): |
| 339 | + preexec = MagicMock() |
| 340 | + self._run_interactive(["x = 1"], preexec=preexec) |
| 341 | + preexec.assert_called_once_with("x = 1") |
| 342 | + |
| 343 | + def test_preexec_exception_does_not_break_repl(self): |
| 344 | + def bad_preexec(cmd): |
| 345 | + raise RuntimeError("hook error") |
| 346 | + |
| 347 | + self._run_interactive(["x = 1", "y = 2"], preexec=bad_preexec) |
| 348 | + |
| 349 | + def test_preexec_not_called_for_repl_commands(self): |
| 350 | + preexec = MagicMock() |
| 351 | + self._run_interactive(["clear"], preexec=preexec) |
| 352 | + preexec.assert_not_called() |
0 commit comments