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