| 
									
										
										
										
											2024-05-22 18:22:01 +02:00
										 |  |  | import contextlib | 
					
						
							|  |  |  | import io | 
					
						
							|  |  |  | import unittest | 
					
						
							|  |  |  | from unittest.mock import patch | 
					
						
							|  |  |  | from textwrap import dedent | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from test.support import force_not_colorized | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-31 23:15:44 +02:00
										 |  |  | from _pyrepl.console import InteractiveColoredConsole | 
					
						
							| 
									
										
										
										
											2024-07-16 08:28:41 +02:00
										 |  |  | from _pyrepl.simple_interact import _more_lines | 
					
						
							| 
									
										
										
										
											2024-05-22 18:22:01 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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="<stdin>") | 
					
						
							| 
									
										
										
										
											2024-05-23 19:58:59 +02:00
										 |  |  |         f = io.StringIO() | 
					
						
							| 
									
										
										
										
											2024-05-22 18:22:01 +02:00
										 |  |  |         with ( | 
					
						
							|  |  |  |             patch.object(InteractiveColoredConsole, "showsyntaxerror") as showsyntaxerror, | 
					
						
							|  |  |  |             patch.object(InteractiveColoredConsole, "runsource", wraps=console.runsource) as runsource, | 
					
						
							| 
									
										
										
										
											2024-05-23 19:58:59 +02:00
										 |  |  |             contextlib.redirect_stdout(f), | 
					
						
							| 
									
										
										
										
											2024-05-22 18:22:01 +02:00
										 |  |  |         ): | 
					
						
							|  |  |  |             more = console.push(code, filename="<stdin>", _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="<stdin>") | 
					
						
							|  |  |  |         f = io.StringIO() | 
					
						
							|  |  |  |         with contextlib.redirect_stdout(f): | 
					
						
							|  |  |  |             more = console.push(code, filename="<stdin>", _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="<stdin>") | 
					
						
							|  |  |  |         f = io.StringIO() | 
					
						
							|  |  |  |         with contextlib.redirect_stdout(f): | 
					
						
							|  |  |  |             more = console.push(code, filename="<stdin>", _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!')" | 
					
						
							| 
									
										
										
										
											2024-05-23 19:58:59 +02:00
										 |  |  |         f = io.StringIO() | 
					
						
							|  |  |  |         with contextlib.redirect_stdout(f): | 
					
						
							|  |  |  |             result = console.runsource(source) | 
					
						
							| 
									
										
										
										
											2024-05-22 18:22:01 +02:00
										 |  |  |         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()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-19 22:01:58 +03:00
										 |  |  |     @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()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-22 18:22:01 +02:00
										 |  |  |     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() | 
					
						
							| 
									
										
										
										
											2024-05-29 13:04:45 +02:00
										 |  |  |         source = dedent("""\
 | 
					
						
							|  |  |  |         match 1: | 
					
						
							|  |  |  |             case {0: _, 0j: _}: | 
					
						
							|  |  |  |                 pass | 
					
						
							|  |  |  |         """)
 | 
					
						
							|  |  |  |         with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror: | 
					
						
							|  |  |  |             console.runsource(source) | 
					
						
							|  |  |  |             mock_showsyntaxerror.assert_called_once() | 
					
						
							| 
									
										
										
										
											2024-05-29 03:26:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-02 15:04:51 +01:00
										 |  |  |     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()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:26:19 +02:00
										 |  |  |     def test_no_active_future(self): | 
					
						
							|  |  |  |         console = InteractiveColoredConsole() | 
					
						
							| 
									
										
										
										
											2024-10-14 20:00:45 +02:00
										 |  |  |         source = dedent("""\
 | 
					
						
							|  |  |  |         x: int = 1 | 
					
						
							|  |  |  |         print(__annotations__) | 
					
						
							|  |  |  |         """)
 | 
					
						
							| 
									
										
										
										
											2024-05-29 03:26:19 +02:00
										 |  |  |         f = io.StringIO() | 
					
						
							|  |  |  |         with contextlib.redirect_stdout(f): | 
					
						
							|  |  |  |             result = console.runsource(source) | 
					
						
							|  |  |  |         self.assertFalse(result) | 
					
						
							|  |  |  |         self.assertEqual(f.getvalue(), "{'x': <class 'int'>}\n") | 
					
						
							| 
									
										
										
										
											2024-07-16 08:28:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-14 20:00:45 +02:00
										 |  |  |     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") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-16 08:28:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class TestMoreLines(unittest.TestCase): | 
					
						
							|  |  |  |     def test_invalid_syntax_single_line(self): | 
					
						
							|  |  |  |         namespace = {} | 
					
						
							|  |  |  |         code = "if foo" | 
					
						
							|  |  |  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | 
					
						
							|  |  |  |         self.assertFalse(_more_lines(console, code)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_empty_line(self): | 
					
						
							|  |  |  |         namespace = {} | 
					
						
							|  |  |  |         code = "" | 
					
						
							|  |  |  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | 
					
						
							|  |  |  |         self.assertFalse(_more_lines(console, code)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_valid_single_statement(self): | 
					
						
							|  |  |  |         namespace = {} | 
					
						
							|  |  |  |         code = "foo = 1" | 
					
						
							|  |  |  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | 
					
						
							|  |  |  |         self.assertFalse(_more_lines(console, code)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_multiline_single_assignment(self): | 
					
						
							|  |  |  |         namespace = {} | 
					
						
							|  |  |  |         code = dedent("""\
 | 
					
						
							|  |  |  |         foo = [ | 
					
						
							|  |  |  |             1, | 
					
						
							|  |  |  |             2, | 
					
						
							|  |  |  |             3, | 
					
						
							|  |  |  |         ]""")
 | 
					
						
							|  |  |  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | 
					
						
							|  |  |  |         self.assertFalse(_more_lines(console, code)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_multiline_single_block(self): | 
					
						
							|  |  |  |         namespace = {} | 
					
						
							|  |  |  |         code = dedent("""\
 | 
					
						
							|  |  |  |         def foo(): | 
					
						
							|  |  |  |             '''docs''' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return 1""")
 | 
					
						
							|  |  |  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | 
					
						
							|  |  |  |         self.assertTrue(_more_lines(console, code)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_multiple_statements_single_line(self): | 
					
						
							|  |  |  |         namespace = {} | 
					
						
							|  |  |  |         code = "foo = 1;bar = 2" | 
					
						
							|  |  |  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | 
					
						
							|  |  |  |         self.assertFalse(_more_lines(console, code)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_multiple_statements(self): | 
					
						
							|  |  |  |         namespace = {} | 
					
						
							|  |  |  |         code = dedent("""\
 | 
					
						
							|  |  |  |         import time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         foo = 1""")
 | 
					
						
							|  |  |  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | 
					
						
							|  |  |  |         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="<stdin>") | 
					
						
							|  |  |  |         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="<stdin>") | 
					
						
							|  |  |  |         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="<stdin>") | 
					
						
							|  |  |  |         self.assertFalse(_more_lines(console, code)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def test_incomplete_statement(self): | 
					
						
							|  |  |  |         namespace = {} | 
					
						
							|  |  |  |         code = "if foo:" | 
					
						
							|  |  |  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | 
					
						
							|  |  |  |         self.assertTrue(_more_lines(console, code)) |