| "Test InteractiveConsole and InteractiveInterpreter from code module" | |
| import sys | |
| import traceback | |
| import unittest | |
| from textwrap import dedent | |
| from contextlib import ExitStack | |
| from unittest import mock | |
| from test.support import force_not_colorized_test_class | |
| from test.support import import_helper | |
| code = import_helper.import_module('code') | |
| class MockSys: | |
| def mock_sys(self): | |
| "Mock system environment for InteractiveConsole" | |
| # use exit stack to match patch context managers to addCleanup | |
| stack = ExitStack() | |
| self.addCleanup(stack.close) | |
| self.infunc = stack.enter_context(mock.patch('code.input', | |
| create=True)) | |
| self.stdout = stack.enter_context(mock.patch('code.sys.stdout')) | |
| self.stderr = stack.enter_context(mock.patch('code.sys.stderr')) | |
| prepatch = mock.patch('code.sys', wraps=code.sys, spec=code.sys) | |
| self.sysmod = stack.enter_context(prepatch) | |
| if sys.excepthook is sys.__excepthook__: | |
| self.sysmod.excepthook = self.sysmod.__excepthook__ | |
| del self.sysmod.ps1 | |
| del self.sysmod.ps2 | |
| @force_not_colorized_test_class | |
| class TestInteractiveConsole(unittest.TestCase, MockSys): | |
| maxDiff = None | |
| def setUp(self): | |
| self.console = code.InteractiveConsole() | |
| self.mock_sys() | |
| def test_ps1(self): | |
| self.infunc.side_effect = [ | |
| "import code", | |
| "code.sys.ps1", | |
| EOFError('Finished') | |
| ] | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stdout.method_calls) | |
| self.assertIn('>>> ', output) | |
| self.assertNotHasAttr(self.sysmod, 'ps1') | |
| self.infunc.side_effect = [ | |
| "import code", | |
| "code.sys.ps1", | |
| EOFError('Finished') | |
| ] | |
| self.sysmod.ps1 = 'custom1> ' | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stdout.method_calls) | |
| self.assertIn('custom1> ', output) | |
| self.assertEqual(self.sysmod.ps1, 'custom1> ') | |
| def test_ps2(self): | |
| self.infunc.side_effect = [ | |
| "import code", | |
| "code.sys.ps2", | |
| EOFError('Finished') | |
| ] | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stdout.method_calls) | |
| self.assertIn('... ', output) | |
| self.assertNotHasAttr(self.sysmod, 'ps2') | |
| self.infunc.side_effect = [ | |
| "import code", | |
| "code.sys.ps2", | |
| EOFError('Finished') | |
| ] | |
| self.sysmod.ps2 = 'custom2> ' | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stdout.method_calls) | |
| self.assertIn('custom2> ', output) | |
| self.assertEqual(self.sysmod.ps2, 'custom2> ') | |
| def test_console_stderr(self): | |
| self.infunc.side_effect = ["'antioch'", "", EOFError('Finished')] | |
| self.console.interact() | |
| for call in list(self.stdout.method_calls): | |
| if 'antioch' in ''.join(call[1]): | |
| break | |
| else: | |
| raise AssertionError("no console stdout") | |
| def test_syntax_error(self): | |
| self.infunc.side_effect = ["def f():", | |
| " x = ?", | |
| "", | |
| EOFError('Finished')] | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) | |
| output = output[output.index('(InteractiveConsole)'):] | |
| output = output[:output.index('\nnow exiting')] | |
| self.assertEqual(output.splitlines()[1:], [ | |
| ' File "<console>", line 2', | |
| ' x = ?', | |
| ' ^', | |
| 'SyntaxError: invalid syntax']) | |
| self.assertIs(self.sysmod.last_type, SyntaxError) | |
| self.assertIs(type(self.sysmod.last_value), SyntaxError) | |
| self.assertIsNone(self.sysmod.last_traceback) | |
| self.assertIsNone(self.sysmod.last_value.__traceback__) | |
| self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) | |
| def test_indentation_error(self): | |
| self.infunc.side_effect = [" 1", EOFError('Finished')] | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) | |
| output = output[output.index('(InteractiveConsole)'):] | |
| output = output[:output.index('\nnow exiting')] | |
| self.assertEqual(output.splitlines()[1:], [ | |
| ' File "<console>", line 1', | |
| ' 1', | |
| 'IndentationError: unexpected indent']) | |
| self.assertIs(self.sysmod.last_type, IndentationError) | |
| self.assertIs(type(self.sysmod.last_value), IndentationError) | |
| self.assertIsNone(self.sysmod.last_traceback) | |
| self.assertIsNone(self.sysmod.last_value.__traceback__) | |
| self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) | |
| def test_unicode_error(self): | |
| self.infunc.side_effect = ["'\ud800'", EOFError('Finished')] | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) | |
| output = output[output.index('(InteractiveConsole)'):] | |
| output = output[output.index('\n') + 1:] | |
| self.assertStartsWith(output, 'UnicodeEncodeError: ') | |
| self.assertIs(self.sysmod.last_type, UnicodeEncodeError) | |
| self.assertIs(type(self.sysmod.last_value), UnicodeEncodeError) | |
| self.assertIsNone(self.sysmod.last_traceback) | |
| self.assertIsNone(self.sysmod.last_value.__traceback__) | |
| self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) | |
| def test_compile_error(self): | |
| # Any error raised by compile() must be reported (gh-69919). | |
| self.infunc.side_effect = ['-' * 100_000 + '1', EOFError('Finished')] | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) | |
| output = output[output.index('(InteractiveConsole)'):] | |
| output = output[output.index('\n') + 1:] | |
| self.assertRegex(output, r'^(MemoryError|RecursionError): ') | |
| self.assertIn(self.sysmod.last_type, (MemoryError, RecursionError)) | |
| self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) | |
| def test_sysexcepthook(self): | |
| self.infunc.side_effect = ["def f():", | |
| " raise ValueError('BOOM!')", | |
| "", | |
| "f()", | |
| EOFError('Finished')] | |
| hook = mock.Mock() | |
| self.sysmod.excepthook = hook | |
| self.console.interact() | |
| hook.assert_called() | |
| hook.assert_called_with(self.sysmod.last_type, | |
| self.sysmod.last_value, | |
| self.sysmod.last_traceback) | |
| self.assertIs(self.sysmod.last_type, ValueError) | |
| self.assertIs(type(self.sysmod.last_value), ValueError) | |
| self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__) | |
| self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) | |
| self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [ | |
| 'Traceback (most recent call last):\n', | |
| ' File "<console>", line 1, in <module>\n', | |
| ' File "<console>", line 2, in f\n', | |
| 'ValueError: BOOM!\n']) | |
| def test_sysexcepthook_syntax_error(self): | |
| self.infunc.side_effect = ["def f():", | |
| " x = ?", | |
| "", | |
| EOFError('Finished')] | |
| hook = mock.Mock() | |
| self.sysmod.excepthook = hook | |
| self.console.interact() | |
| hook.assert_called() | |
| hook.assert_called_with(self.sysmod.last_type, | |
| self.sysmod.last_value, | |
| self.sysmod.last_traceback) | |
| self.assertIs(self.sysmod.last_type, SyntaxError) | |
| self.assertIs(type(self.sysmod.last_value), SyntaxError) | |
| self.assertIsNone(self.sysmod.last_traceback) | |
| self.assertIsNone(self.sysmod.last_value.__traceback__) | |
| self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) | |
| self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [ | |
| ' File "<console>", line 2\n', | |
| ' x = ?\n', | |
| ' ^\n', | |
| 'SyntaxError: invalid syntax\n']) | |
| def test_sysexcepthook_indentation_error(self): | |
| self.infunc.side_effect = [" 1", EOFError('Finished')] | |
| hook = mock.Mock() | |
| self.sysmod.excepthook = hook | |
| self.console.interact() | |
| hook.assert_called() | |
| hook.assert_called_with(self.sysmod.last_type, | |
| self.sysmod.last_value, | |
| self.sysmod.last_traceback) | |
| self.assertIs(self.sysmod.last_type, IndentationError) | |
| self.assertIs(type(self.sysmod.last_value), IndentationError) | |
| self.assertIsNone(self.sysmod.last_traceback) | |
| self.assertIsNone(self.sysmod.last_value.__traceback__) | |
| self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) | |
| self.assertEqual(traceback.format_exception(self.sysmod.last_exc), [ | |
| ' File "<console>", line 1\n', | |
| ' 1\n', | |
| 'IndentationError: unexpected indent\n']) | |
| def test_sysexcepthook_crashing_doesnt_close_repl(self): | |
| self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')] | |
| self.sysmod.excepthook = 1 | |
| self.console.interact() | |
| self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0]) | |
| error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write') | |
| self.assertIn("Error in sys.excepthook:", error) | |
| self.assertEqual(error.count("'int' object is not callable"), 1) | |
| self.assertIn("Original exception was:", error) | |
| self.assertIn("division by zero", error) | |
| def test_sysexcepthook_raising_BaseException(self): | |
| self.infunc.side_effect = ["1/0", "a = 123", "print(a)", EOFError('Finished')] | |
| s = "not so fast" | |
| def raise_base(*args, **kwargs): | |
| raise BaseException(s) | |
| self.sysmod.excepthook = raise_base | |
| self.console.interact() | |
| self.assertEqual(['write', ('123', ), {}], self.stdout.method_calls[0]) | |
| error = "".join(call.args[0] for call in self.stderr.method_calls if call[0] == 'write') | |
| self.assertIn("Error in sys.excepthook:", error) | |
| self.assertEqual(error.count("not so fast"), 1) | |
| self.assertIn("Original exception was:", error) | |
| self.assertIn("division by zero", error) | |
| def test_sysexcepthook_raising_SystemExit_gets_through(self): | |
| self.infunc.side_effect = ["1/0"] | |
| def raise_base(*args, **kwargs): | |
| raise SystemExit | |
| self.sysmod.excepthook = raise_base | |
| with self.assertRaises(SystemExit): | |
| self.console.interact() | |
| def test_banner(self): | |
| # with banner | |
| self.infunc.side_effect = EOFError('Finished') | |
| self.console.interact(banner='Foo') | |
| self.assertEqual(len(self.stderr.method_calls), 3) | |
| banner_call = self.stderr.method_calls[0] | |
| self.assertEqual(banner_call, ['write', ('Foo\n',), {}]) | |
| # no banner | |
| self.stderr.reset_mock() | |
| self.infunc.side_effect = EOFError('Finished') | |
| self.console.interact(banner='') | |
| self.assertEqual(len(self.stderr.method_calls), 2) | |
| def test_exit_msg(self): | |
| # default exit message | |
| self.infunc.side_effect = EOFError('Finished') | |
| self.console.interact(banner='') | |
| self.assertEqual(len(self.stderr.method_calls), 2) | |
| err_msg = self.stderr.method_calls[1] | |
| expected = 'now exiting InteractiveConsole...\n' | |
| self.assertEqual(err_msg, ['write', (expected,), {}]) | |
| # no exit message | |
| self.stderr.reset_mock() | |
| self.infunc.side_effect = EOFError('Finished') | |
| self.console.interact(banner='', exitmsg='') | |
| self.assertEqual(len(self.stderr.method_calls), 1) | |
| # custom exit message | |
| self.stderr.reset_mock() | |
| message = ( | |
| 'bye! \N{GREEK SMALL LETTER ZETA}\N{CYRILLIC SMALL LETTER ZHE}' | |
| ) | |
| self.infunc.side_effect = EOFError('Finished') | |
| self.console.interact(banner='', exitmsg=message) | |
| self.assertEqual(len(self.stderr.method_calls), 2) | |
| err_msg = self.stderr.method_calls[1] | |
| expected = message + '\n' | |
| self.assertEqual(err_msg, ['write', (expected,), {}]) | |
| def test_cause_tb(self): | |
| self.infunc.side_effect = ["raise ValueError('') from AttributeError", | |
| EOFError('Finished')] | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) | |
| expected = dedent(""" | |
| AttributeError | |
| The above exception was the direct cause of the following exception: | |
| Traceback (most recent call last): | |
| File "<console>", line 1, in <module> | |
| ValueError | |
| """) | |
| self.assertIn(expected, output) | |
| self.assertIs(self.sysmod.last_type, ValueError) | |
| self.assertIs(type(self.sysmod.last_value), ValueError) | |
| self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__) | |
| self.assertIsNotNone(self.sysmod.last_traceback) | |
| self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) | |
| def test_context_tb(self): | |
| self.infunc.side_effect = ["try: ham\nexcept: eggs\n", | |
| EOFError('Finished')] | |
| self.console.interact() | |
| output = ''.join(''.join(call[1]) for call in self.stderr.method_calls) | |
| expected = dedent(""" | |
| Traceback (most recent call last): | |
| File "<console>", line 1, in <module> | |
| NameError: name 'ham' is not defined | |
| During handling of the above exception, another exception occurred: | |
| Traceback (most recent call last): | |
| File "<console>", line 2, in <module> | |
| NameError: name 'eggs' is not defined | |
| """) | |
| self.assertIn(expected, output) | |
| self.assertIs(self.sysmod.last_type, NameError) | |
| self.assertIs(type(self.sysmod.last_value), NameError) | |
| self.assertIs(self.sysmod.last_traceback, self.sysmod.last_value.__traceback__) | |
| self.assertIsNotNone(self.sysmod.last_traceback) | |
| self.assertIs(self.sysmod.last_exc, self.sysmod.last_value) | |
| class TestInteractiveConsoleLocalExit(unittest.TestCase, MockSys): | |
| def setUp(self): | |
| self.console = code.InteractiveConsole(local_exit=True) | |
| self.mock_sys() | |
| @unittest.skipIf(sys.flags.no_site, "exit() isn't defined unless there's a site module") | |
| def test_exit(self): | |
| # default exit message | |
| self.infunc.side_effect = ["exit()"] | |
| self.console.interact(banner='') | |
| self.assertEqual(len(self.stderr.method_calls), 2) | |
| err_msg = self.stderr.method_calls[1] | |
| expected = 'now exiting InteractiveConsole...\n' | |
| self.assertEqual(err_msg, ['write', (expected,), {}]) | |
| if __name__ == "__main__": | |
| unittest.main() |