Revert GH-131993. Fix swallowing some syntax warnings in different modules if they accidentally have the same message and are emitted from the same line.
276 lines
9.0 KiB
Python
276 lines
9.0 KiB
Python
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="<stdin>")
|
|
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="<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")
|
|
|
|
@force_not_colorized
|
|
def test_multiple_statements_fail_early(self):
|
|
console = InteractiveColoredConsole()
|
|
code = dedent("""\
|
|
raise Exception('foobar')
|
|
print('spam', 'eggs', sep='&')
|
|
""")
|
|
f = io.StringIO()
|
|
with contextlib.redirect_stderr(f):
|
|
console.runsource(code)
|
|
self.assertIn('Exception: foobar', f.getvalue())
|
|
self.assertNotIn('spam&eggs', f.getvalue())
|
|
|
|
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!')"
|
|
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 parameter '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': <class 'int'>}\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="<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))
|