• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import contextlib
2import io
3import unittest
4from unittest.mock import patch
5from textwrap import dedent
6
7from test.support import force_not_colorized
8
9from _pyrepl.console import InteractiveColoredConsole
10from _pyrepl.simple_interact import _more_lines
11
12class TestSimpleInteract(unittest.TestCase):
13    def test_multiple_statements(self):
14        namespace = {}
15        code = dedent("""\
16        class A:
17            def foo(self):
18
19
20                pass
21
22        class B:
23            def bar(self):
24                pass
25
26        a = 1
27        a
28        """)
29        console = InteractiveColoredConsole(namespace, filename="<stdin>")
30        f = io.StringIO()
31        with (
32            patch.object(InteractiveColoredConsole, "showsyntaxerror") as showsyntaxerror,
33            patch.object(InteractiveColoredConsole, "runsource", wraps=console.runsource) as runsource,
34            contextlib.redirect_stdout(f),
35        ):
36            more = console.push(code, filename="<stdin>", _symbol="single")  # type: ignore[call-arg]
37        self.assertFalse(more)
38        showsyntaxerror.assert_not_called()
39
40
41    def test_multiple_statements_output(self):
42        namespace = {}
43        code = dedent("""\
44        b = 1
45        b
46        a = 1
47        a
48        """)
49        console = InteractiveColoredConsole(namespace, filename="<stdin>")
50        f = io.StringIO()
51        with contextlib.redirect_stdout(f):
52            more = console.push(code, filename="<stdin>", _symbol="single")  # type: ignore[call-arg]
53        self.assertFalse(more)
54        self.assertEqual(f.getvalue(), "1\n")
55
56    def test_empty(self):
57        namespace = {}
58        code = ""
59        console = InteractiveColoredConsole(namespace, filename="<stdin>")
60        f = io.StringIO()
61        with contextlib.redirect_stdout(f):
62            more = console.push(code, filename="<stdin>", _symbol="single")  # type: ignore[call-arg]
63        self.assertFalse(more)
64        self.assertEqual(f.getvalue(), "")
65
66    def test_runsource_compiles_and_runs_code(self):
67        console = InteractiveColoredConsole()
68        source = "print('Hello, world!')"
69        with patch.object(console, "runcode") as mock_runcode:
70            console.runsource(source)
71            mock_runcode.assert_called_once()
72
73    def test_runsource_returns_false_for_successful_compilation(self):
74        console = InteractiveColoredConsole()
75        source = "print('Hello, world!')"
76        f = io.StringIO()
77        with contextlib.redirect_stdout(f):
78            result = console.runsource(source)
79        self.assertFalse(result)
80
81    @force_not_colorized
82    def test_runsource_returns_false_for_failed_compilation(self):
83        console = InteractiveColoredConsole()
84        source = "print('Hello, world!'"
85        f = io.StringIO()
86        with contextlib.redirect_stderr(f):
87            result = console.runsource(source)
88        self.assertFalse(result)
89        self.assertIn('SyntaxError', f.getvalue())
90
91    @force_not_colorized
92    def test_runsource_show_syntax_error_location(self):
93        console = InteractiveColoredConsole()
94        source = "def f(x, x): ..."
95        f = io.StringIO()
96        with contextlib.redirect_stderr(f):
97            result = console.runsource(source)
98        self.assertFalse(result)
99        r = """
100    def f(x, x): ...
101             ^
102SyntaxError: duplicate argument 'x' in function definition"""
103        self.assertIn(r, f.getvalue())
104
105    def test_runsource_shows_syntax_error_for_failed_compilation(self):
106        console = InteractiveColoredConsole()
107        source = "print('Hello, world!'"
108        with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror:
109            console.runsource(source)
110            mock_showsyntaxerror.assert_called_once()
111        source = dedent("""\
112        match 1:
113            case {0: _, 0j: _}:
114                pass
115        """)
116        with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror:
117            console.runsource(source)
118            mock_showsyntaxerror.assert_called_once()
119
120    def test_runsource_survives_null_bytes(self):
121        console = InteractiveColoredConsole()
122        source = "\x00\n"
123        f = io.StringIO()
124        with contextlib.redirect_stdout(f), contextlib.redirect_stderr(f):
125            result = console.runsource(source)
126        self.assertFalse(result)
127        self.assertIn("source code string cannot contain null bytes", f.getvalue())
128
129    def test_no_active_future(self):
130        console = InteractiveColoredConsole()
131        source = dedent("""\
132        x: int = 1
133        print(__annotations__)
134        """)
135        f = io.StringIO()
136        with contextlib.redirect_stdout(f):
137            result = console.runsource(source)
138        self.assertFalse(result)
139        self.assertEqual(f.getvalue(), "{'x': <class 'int'>}\n")
140
141    def test_future_annotations(self):
142        console = InteractiveColoredConsole()
143        source = dedent("""\
144        from __future__ import annotations
145        def g(x: int): ...
146        print(g.__annotations__)
147        """)
148        f = io.StringIO()
149        with contextlib.redirect_stdout(f):
150            result = console.runsource(source)
151        self.assertFalse(result)
152        self.assertEqual(f.getvalue(), "{'x': 'int'}\n")
153
154    def test_future_barry_as_flufl(self):
155        console = InteractiveColoredConsole()
156        f = io.StringIO()
157        with contextlib.redirect_stdout(f):
158            result = console.runsource("from __future__ import barry_as_FLUFL\n")
159            result = console.runsource("""print("black" <> 'blue')\n""")
160        self.assertFalse(result)
161        self.assertEqual(f.getvalue(), "True\n")
162
163
164class TestMoreLines(unittest.TestCase):
165    def test_invalid_syntax_single_line(self):
166        namespace = {}
167        code = "if foo"
168        console = InteractiveColoredConsole(namespace, filename="<stdin>")
169        self.assertFalse(_more_lines(console, code))
170
171    def test_empty_line(self):
172        namespace = {}
173        code = ""
174        console = InteractiveColoredConsole(namespace, filename="<stdin>")
175        self.assertFalse(_more_lines(console, code))
176
177    def test_valid_single_statement(self):
178        namespace = {}
179        code = "foo = 1"
180        console = InteractiveColoredConsole(namespace, filename="<stdin>")
181        self.assertFalse(_more_lines(console, code))
182
183    def test_multiline_single_assignment(self):
184        namespace = {}
185        code = dedent("""\
186        foo = [
187            1,
188            2,
189            3,
190        ]""")
191        console = InteractiveColoredConsole(namespace, filename="<stdin>")
192        self.assertFalse(_more_lines(console, code))
193
194    def test_multiline_single_block(self):
195        namespace = {}
196        code = dedent("""\
197        def foo():
198            '''docs'''
199
200            return 1""")
201        console = InteractiveColoredConsole(namespace, filename="<stdin>")
202        self.assertTrue(_more_lines(console, code))
203
204    def test_multiple_statements_single_line(self):
205        namespace = {}
206        code = "foo = 1;bar = 2"
207        console = InteractiveColoredConsole(namespace, filename="<stdin>")
208        self.assertFalse(_more_lines(console, code))
209
210    def test_multiple_statements(self):
211        namespace = {}
212        code = dedent("""\
213        import time
214
215        foo = 1""")
216        console = InteractiveColoredConsole(namespace, filename="<stdin>")
217        self.assertTrue(_more_lines(console, code))
218
219    def test_multiple_blocks(self):
220        namespace = {}
221        code = dedent("""\
222        from dataclasses import dataclass
223
224        @dataclass
225        class Point:
226            x: float
227            y: float""")
228        console = InteractiveColoredConsole(namespace, filename="<stdin>")
229        self.assertTrue(_more_lines(console, code))
230
231    def test_multiple_blocks_empty_newline(self):
232        namespace = {}
233        code = dedent("""\
234        from dataclasses import dataclass
235
236        @dataclass
237        class Point:
238            x: float
239            y: float
240        """)
241        console = InteractiveColoredConsole(namespace, filename="<stdin>")
242        self.assertFalse(_more_lines(console, code))
243
244    def test_multiple_blocks_indented_newline(self):
245        namespace = {}
246        code = (
247            "from dataclasses import dataclass\n"
248            "\n"
249            "@dataclass\n"
250            "class Point:\n"
251            "    x: float\n"
252            "    y: float\n"
253            "    "
254        )
255        console = InteractiveColoredConsole(namespace, filename="<stdin>")
256        self.assertFalse(_more_lines(console, code))
257
258    def test_incomplete_statement(self):
259        namespace = {}
260        code = "if foo:"
261        console = InteractiveColoredConsole(namespace, filename="<stdin>")
262        self.assertTrue(_more_lines(console, code))
263