import contextlib import io import unittest from unittest.mock import patch from textwrap import dedent from test.support import force_not_colorized from _pyrepl.console import InteractiveColoredConsole from _pyrepl.simple_interact import _more_lines class TestSimpleInteract(unittest.TestCase): def test_multiple_statements(self): namespace = {} code = dedent("""\ class A: def foo(self): pass class B: def bar(self): pass a = 1 a """) console = InteractiveColoredConsole(namespace, filename="") f = io.StringIO() with ( patch.object(InteractiveColoredConsole, "showsyntaxerror") as showsyntaxerror, patch.object(InteractiveColoredConsole, "runsource", wraps=console.runsource) as runsource, contextlib.redirect_stdout(f), ): more = console.push(code, filename="", _symbol="single") # type: ignore[call-arg] self.assertFalse(more) showsyntaxerror.assert_not_called() def test_multiple_statements_output(self): namespace = {} code = dedent("""\ b = 1 b a = 1 a """) console = InteractiveColoredConsole(namespace, filename="") f = io.StringIO() with contextlib.redirect_stdout(f): more = console.push(code, filename="", _symbol="single") # type: ignore[call-arg] self.assertFalse(more) self.assertEqual(f.getvalue(), "1\n") def test_empty(self): namespace = {} code = "" console = InteractiveColoredConsole(namespace, filename="") f = io.StringIO() with contextlib.redirect_stdout(f): more = console.push(code, filename="", _symbol="single") # type: ignore[call-arg] self.assertFalse(more) self.assertEqual(f.getvalue(), "") def test_runsource_compiles_and_runs_code(self): console = InteractiveColoredConsole() source = "print('Hello, world!')" with patch.object(console, "runcode") as mock_runcode: console.runsource(source) mock_runcode.assert_called_once() def test_runsource_returns_false_for_successful_compilation(self): console = InteractiveColoredConsole() source = "print('Hello, world!')" f = io.StringIO() with contextlib.redirect_stdout(f): result = console.runsource(source) self.assertFalse(result) @force_not_colorized def test_runsource_returns_false_for_failed_compilation(self): console = InteractiveColoredConsole() source = "print('Hello, world!'" f = io.StringIO() with contextlib.redirect_stderr(f): result = console.runsource(source) self.assertFalse(result) self.assertIn('SyntaxError', f.getvalue()) @force_not_colorized def test_runsource_show_syntax_error_location(self): console = InteractiveColoredConsole() source = "def f(x, x): ..." f = io.StringIO() with contextlib.redirect_stderr(f): result = console.runsource(source) self.assertFalse(result) r = """ def f(x, x): ... ^ SyntaxError: duplicate argument 'x' in function definition""" self.assertIn(r, f.getvalue()) def test_runsource_shows_syntax_error_for_failed_compilation(self): console = InteractiveColoredConsole() source = "print('Hello, world!'" with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror: console.runsource(source) mock_showsyntaxerror.assert_called_once() source = dedent("""\ match 1: case {0: _, 0j: _}: pass """) with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror: console.runsource(source) mock_showsyntaxerror.assert_called_once() def test_runsource_survives_null_bytes(self): console = InteractiveColoredConsole() source = "\x00\n" f = io.StringIO() with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f): result = console.runsource(source) self.assertFalse(result) self.assertIn("source code string cannot contain null bytes", f.getvalue()) def test_no_active_future(self): console = InteractiveColoredConsole() source = dedent("""\ x: int = 1 print(__annotate__(1)) """) f = io.StringIO() with contextlib.redirect_stdout(f): result = console.runsource(source) self.assertFalse(result) self.assertEqual(f.getvalue(), "{'x': }\n") def test_future_annotations(self): console = InteractiveColoredConsole() source = dedent("""\ from __future__ import annotations def g(x: int): ... print(g.__annotations__) """) f = io.StringIO() with contextlib.redirect_stdout(f): result = console.runsource(source) self.assertFalse(result) self.assertEqual(f.getvalue(), "{'x': 'int'}\n") def test_future_barry_as_flufl(self): console = InteractiveColoredConsole() f = io.StringIO() with contextlib.redirect_stdout(f): result = console.runsource("from __future__ import barry_as_FLUFL\n") result = console.runsource("""print("black" <> 'blue')\n""") self.assertFalse(result) self.assertEqual(f.getvalue(), "True\n") class TestMoreLines(unittest.TestCase): def test_invalid_syntax_single_line(self): namespace = {} code = "if foo" console = InteractiveColoredConsole(namespace, filename="") self.assertFalse(_more_lines(console, code)) def test_empty_line(self): namespace = {} code = "" console = InteractiveColoredConsole(namespace, filename="") self.assertFalse(_more_lines(console, code)) def test_valid_single_statement(self): namespace = {} code = "foo = 1" console = InteractiveColoredConsole(namespace, filename="") self.assertFalse(_more_lines(console, code)) def test_multiline_single_assignment(self): namespace = {} code = dedent("""\ foo = [ 1, 2, 3, ]""") console = InteractiveColoredConsole(namespace, filename="") self.assertFalse(_more_lines(console, code)) def test_multiline_single_block(self): namespace = {} code = dedent("""\ def foo(): '''docs''' return 1""") console = InteractiveColoredConsole(namespace, filename="") self.assertTrue(_more_lines(console, code)) def test_multiple_statements_single_line(self): namespace = {} code = "foo = 1;bar = 2" console = InteractiveColoredConsole(namespace, filename="") self.assertFalse(_more_lines(console, code)) def test_multiple_statements(self): namespace = {} code = dedent("""\ import time foo = 1""") console = InteractiveColoredConsole(namespace, filename="") self.assertTrue(_more_lines(console, code)) def test_multiple_blocks(self): namespace = {} code = dedent("""\ from dataclasses import dataclass @dataclass class Point: x: float y: float""") console = InteractiveColoredConsole(namespace, filename="") self.assertTrue(_more_lines(console, code)) def test_multiple_blocks_empty_newline(self): namespace = {} code = dedent("""\ from dataclasses import dataclass @dataclass class Point: x: float y: float """) console = InteractiveColoredConsole(namespace, filename="") self.assertFalse(_more_lines(console, code)) def test_multiple_blocks_indented_newline(self): namespace = {} code = ( "from dataclasses import dataclass\n" "\n" "@dataclass\n" "class Point:\n" " x: float\n" " y: float\n" " " ) console = InteractiveColoredConsole(namespace, filename="") self.assertFalse(_more_lines(console, code)) def test_incomplete_statement(self): namespace = {} code = "if foo:" console = InteractiveColoredConsole(namespace, filename="") self.assertTrue(_more_lines(console, code))