summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2019-10-08 11:32:25 (GMT)
committerGitHub <noreply@github.com>2019-10-08 11:32:25 (GMT)
commitb690a2759e62d9ee0b6ea1b20e8f7e4b2cdbf8bb (patch)
treeef8fa0a711cb3780ac5bb33147bea57378322c62 /Lib/idlelib
parentd05b000c6bcd39dba4f4b2656e45954802649562 (diff)
downloadcpython-b690a2759e62d9ee0b6ea1b20e8f7e4b2cdbf8bb.zip
cpython-b690a2759e62d9ee0b6ea1b20e8f7e4b2cdbf8bb.tar.gz
cpython-b690a2759e62d9ee0b6ea1b20e8f7e4b2cdbf8bb.tar.bz2
bpo-36698: IDLE no longer fails when write non-encodable characters to stderr. (GH-16583)
It now escapes them with a backslash, as the regular Python interpreter. Added the "errors" field to the standard streams.
Diffstat (limited to 'Lib/idlelib')
-rw-r--r--Lib/idlelib/idle_test/test_run.py60
-rw-r--r--Lib/idlelib/iomenu.py41
-rwxr-xr-xLib/idlelib/pyshell.py14
-rw-r--r--Lib/idlelib/run.py36
4 files changed, 84 insertions, 67 deletions
diff --git a/Lib/idlelib/idle_test/test_run.py b/Lib/idlelib/idle_test/test_run.py
index cad0b4d..9995dbe 100644
--- a/Lib/idlelib/idle_test/test_run.py
+++ b/Lib/idlelib/idle_test/test_run.py
@@ -36,7 +36,7 @@ class RunTest(unittest.TestCase):
self.assertIn('UnhashableException: ex1', tb[10])
-# PseudoFile tests.
+# StdioFile tests.
class S(str):
def __str__(self):
@@ -68,14 +68,14 @@ class MockShell:
self.lines = list(lines)[::-1]
-class PseudeInputFilesTest(unittest.TestCase):
+class StdInputFilesTest(unittest.TestCase):
def test_misc(self):
shell = MockShell()
- f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
+ f = run.StdInputFile(shell, 'stdin')
self.assertIsInstance(f, io.TextIOBase)
self.assertEqual(f.encoding, 'utf-8')
- self.assertIsNone(f.errors)
+ self.assertEqual(f.errors, 'strict')
self.assertIsNone(f.newlines)
self.assertEqual(f.name, '<stdin>')
self.assertFalse(f.closed)
@@ -86,7 +86,7 @@ class PseudeInputFilesTest(unittest.TestCase):
def test_unsupported(self):
shell = MockShell()
- f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
+ f = run.StdInputFile(shell, 'stdin')
self.assertRaises(OSError, f.fileno)
self.assertRaises(OSError, f.tell)
self.assertRaises(OSError, f.seek, 0)
@@ -95,7 +95,7 @@ class PseudeInputFilesTest(unittest.TestCase):
def test_read(self):
shell = MockShell()
- f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
+ f = run.StdInputFile(shell, 'stdin')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.read(), 'one\ntwo\n')
shell.push(['one\n', 'two\n', ''])
@@ -115,7 +115,7 @@ class PseudeInputFilesTest(unittest.TestCase):
def test_readline(self):
shell = MockShell()
- f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
+ f = run.StdInputFile(shell, 'stdin')
shell.push(['one\n', 'two\n', 'three\n', 'four\n'])
self.assertEqual(f.readline(), 'one\n')
self.assertEqual(f.readline(-1), 'two\n')
@@ -140,7 +140,7 @@ class PseudeInputFilesTest(unittest.TestCase):
def test_readlines(self):
shell = MockShell()
- f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
+ f = run.StdInputFile(shell, 'stdin')
shell.push(['one\n', 'two\n', ''])
self.assertEqual(f.readlines(), ['one\n', 'two\n'])
shell.push(['one\n', 'two\n', ''])
@@ -161,7 +161,7 @@ class PseudeInputFilesTest(unittest.TestCase):
def test_close(self):
shell = MockShell()
- f = run.PseudoInputFile(shell, 'stdin', 'utf-8')
+ f = run.StdInputFile(shell, 'stdin')
shell.push(['one\n', 'two\n', ''])
self.assertFalse(f.closed)
self.assertEqual(f.readline(), 'one\n')
@@ -171,14 +171,14 @@ class PseudeInputFilesTest(unittest.TestCase):
self.assertRaises(TypeError, f.close, 1)
-class PseudeOutputFilesTest(unittest.TestCase):
+class StdOutputFilesTest(unittest.TestCase):
def test_misc(self):
shell = MockShell()
- f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
+ f = run.StdOutputFile(shell, 'stdout')
self.assertIsInstance(f, io.TextIOBase)
self.assertEqual(f.encoding, 'utf-8')
- self.assertIsNone(f.errors)
+ self.assertEqual(f.errors, 'strict')
self.assertIsNone(f.newlines)
self.assertEqual(f.name, '<stdout>')
self.assertFalse(f.closed)
@@ -189,7 +189,7 @@ class PseudeOutputFilesTest(unittest.TestCase):
def test_unsupported(self):
shell = MockShell()
- f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
+ f = run.StdOutputFile(shell, 'stdout')
self.assertRaises(OSError, f.fileno)
self.assertRaises(OSError, f.tell)
self.assertRaises(OSError, f.seek, 0)
@@ -198,16 +198,36 @@ class PseudeOutputFilesTest(unittest.TestCase):
def test_write(self):
shell = MockShell()
- f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
+ f = run.StdOutputFile(shell, 'stdout')
f.write('test')
self.assertEqual(shell.written, [('test', 'stdout')])
shell.reset()
- f.write('t\xe8st')
- self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
+ f.write('t\xe8\u015b\U0001d599')
+ self.assertEqual(shell.written, [('t\xe8\u015b\U0001d599', 'stdout')])
shell.reset()
- f.write(S('t\xe8st'))
- self.assertEqual(shell.written, [('t\xe8st', 'stdout')])
+ f.write(S('t\xe8\u015b\U0001d599'))
+ self.assertEqual(shell.written, [('t\xe8\u015b\U0001d599', 'stdout')])
+ self.assertEqual(type(shell.written[0][0]), str)
+ shell.reset()
+
+ self.assertRaises(TypeError, f.write)
+ self.assertEqual(shell.written, [])
+ self.assertRaises(TypeError, f.write, b'test')
+ self.assertRaises(TypeError, f.write, 123)
+ self.assertEqual(shell.written, [])
+ self.assertRaises(TypeError, f.write, 'test', 'spam')
+ self.assertEqual(shell.written, [])
+
+ def test_write_stderr_nonencodable(self):
+ shell = MockShell()
+ f = run.StdOutputFile(shell, 'stderr', 'iso-8859-15', 'backslashreplace')
+ f.write('t\xe8\u015b\U0001d599\xa4')
+ self.assertEqual(shell.written, [('t\xe8\\u015b\\U0001d599\\xa4', 'stderr')])
+ shell.reset()
+
+ f.write(S('t\xe8\u015b\U0001d599\xa4'))
+ self.assertEqual(shell.written, [('t\xe8\\u015b\\U0001d599\\xa4', 'stderr')])
self.assertEqual(type(shell.written[0][0]), str)
shell.reset()
@@ -221,7 +241,7 @@ class PseudeOutputFilesTest(unittest.TestCase):
def test_writelines(self):
shell = MockShell()
- f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
+ f = run.StdOutputFile(shell, 'stdout')
f.writelines([])
self.assertEqual(shell.written, [])
shell.reset()
@@ -251,7 +271,7 @@ class PseudeOutputFilesTest(unittest.TestCase):
def test_close(self):
shell = MockShell()
- f = run.PseudoOutputFile(shell, 'stdout', 'utf-8')
+ f = run.StdOutputFile(shell, 'stdout')
self.assertFalse(f.closed)
f.write('test')
f.close()
diff --git a/Lib/idlelib/iomenu.py b/Lib/idlelib/iomenu.py
index b9e813b..b5533be 100644
--- a/Lib/idlelib/iomenu.py
+++ b/Lib/idlelib/iomenu.py
@@ -15,6 +15,7 @@ from idlelib.config import idleConf
if idlelib.testing: # Set True by test.test_idle to avoid setlocale.
encoding = 'utf-8'
+ errors = 'surrogateescape'
else:
# Try setting the locale, so that we can find out
# what encoding to use
@@ -24,15 +25,9 @@ else:
except (ImportError, locale.Error):
pass
- locale_decode = 'ascii'
if sys.platform == 'win32':
- # On Windows, we could use "mbcs". However, to give the user
- # a portable encoding name, we need to find the code page
- try:
- locale_encoding = locale.getdefaultlocale()[1]
- codecs.lookup(locale_encoding)
- except LookupError:
- pass
+ encoding = 'utf-8'
+ errors = 'surrogateescape'
else:
try:
# Different things can fail here: the locale module may not be
@@ -40,30 +35,30 @@ else:
# resulting codeset may be unknown to Python. We ignore all
# these problems, falling back to ASCII
locale_encoding = locale.nl_langinfo(locale.CODESET)
- if locale_encoding is None or locale_encoding == '':
- # situation occurs on macOS
- locale_encoding = 'ascii'
- codecs.lookup(locale_encoding)
+ if locale_encoding:
+ codecs.lookup(locale_encoding)
except (NameError, AttributeError, LookupError):
# Try getdefaultlocale: it parses environment variables,
# which may give a clue. Unfortunately, getdefaultlocale has
# bugs that can cause ValueError.
try:
locale_encoding = locale.getdefaultlocale()[1]
- if locale_encoding is None or locale_encoding == '':
- # situation occurs on macOS
- locale_encoding = 'ascii'
- codecs.lookup(locale_encoding)
+ if locale_encoding:
+ codecs.lookup(locale_encoding)
except (ValueError, LookupError):
pass
- locale_encoding = locale_encoding.lower()
-
- encoding = locale_encoding
- # Encoding is used in multiple files; locale_encoding nowhere.
- # The only use of 'encoding' below is in _decode as initial value
- # of deprecated block asking user for encoding.
- # Perhaps use elsewhere should be reviewed.
+ if locale_encoding:
+ encoding = locale_encoding.lower()
+ errors = 'strict'
+ else:
+ # POSIX locale or macOS
+ encoding = 'ascii'
+ errors = 'surrogateescape'
+ # Encoding is used in multiple files; locale_encoding nowhere.
+ # The only use of 'encoding' below is in _decode as initial value
+ # of deprecated block asking user for encoding.
+ # Perhaps use elsewhere should be reviewed.
coding_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII)
blank_re = re.compile(r'^[ \t\f]*(?:[#\r\n]|$)', re.ASCII)
diff --git a/Lib/idlelib/pyshell.py b/Lib/idlelib/pyshell.py
index bc87d24..065122d 100755
--- a/Lib/idlelib/pyshell.py
+++ b/Lib/idlelib/pyshell.py
@@ -54,7 +54,7 @@ from idlelib.editor import EditorWindow, fixwordbreaks
from idlelib.filelist import FileList
from idlelib.outwin import OutputWindow
from idlelib import rpc
-from idlelib.run import idle_formatwarning, PseudoInputFile, PseudoOutputFile
+from idlelib.run import idle_formatwarning, StdInputFile, StdOutputFile
from idlelib.undo import UndoDelegator
HOST = '127.0.0.1' # python execution server on localhost loopback
@@ -902,10 +902,14 @@ class PyShell(OutputWindow):
self.save_stderr = sys.stderr
self.save_stdin = sys.stdin
from idlelib import iomenu
- self.stdin = PseudoInputFile(self, "stdin", iomenu.encoding)
- self.stdout = PseudoOutputFile(self, "stdout", iomenu.encoding)
- self.stderr = PseudoOutputFile(self, "stderr", iomenu.encoding)
- self.console = PseudoOutputFile(self, "console", iomenu.encoding)
+ self.stdin = StdInputFile(self, "stdin",
+ iomenu.encoding, iomenu.errors)
+ self.stdout = StdOutputFile(self, "stdout",
+ iomenu.encoding, iomenu.errors)
+ self.stderr = StdOutputFile(self, "stderr",
+ iomenu.encoding, "backslashreplace")
+ self.console = StdOutputFile(self, "console",
+ iomenu.encoding, iomenu.errors)
if not use_subprocess:
sys.stdout = self.stdout
sys.stderr = self.stderr
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
index 41e0ded..5bd84aa 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -401,18 +401,23 @@ class MyRPCServer(rpc.RPCServer):
# Pseudofiles for shell-remote communication (also used in pyshell)
-class PseudoFile(io.TextIOBase):
+class StdioFile(io.TextIOBase):
- def __init__(self, shell, tags, encoding=None):
+ def __init__(self, shell, tags, encoding='utf-8', errors='strict'):
self.shell = shell
self.tags = tags
self._encoding = encoding
+ self._errors = errors
@property
def encoding(self):
return self._encoding
@property
+ def errors(self):
+ return self._errors
+
+ @property
def name(self):
return '<%s>' % self.tags
@@ -420,7 +425,7 @@ class PseudoFile(io.TextIOBase):
return True
-class PseudoOutputFile(PseudoFile):
+class StdOutputFile(StdioFile):
def writable(self):
return True
@@ -428,19 +433,12 @@ class PseudoOutputFile(PseudoFile):
def write(self, s):
if self.closed:
raise ValueError("write to closed file")
- if type(s) is not str:
- if not isinstance(s, str):
- raise TypeError('must be str, not ' + type(s).__name__)
- # See issue #19481
- s = str.__str__(s)
+ s = str.encode(s, self.encoding, self.errors).decode(self.encoding, self.errors)
return self.shell.write(s, self.tags)
-class PseudoInputFile(PseudoFile):
-
- def __init__(self, shell, tags, encoding=None):
- PseudoFile.__init__(self, shell, tags, encoding)
- self._line_buffer = ''
+class StdInputFile(StdioFile):
+ _line_buffer = ''
def readable(self):
return True
@@ -495,12 +493,12 @@ class MyHandler(rpc.RPCHandler):
executive = Executive(self)
self.register("exec", executive)
self.console = self.get_remote_proxy("console")
- sys.stdin = PseudoInputFile(self.console, "stdin",
- iomenu.encoding)
- sys.stdout = PseudoOutputFile(self.console, "stdout",
- iomenu.encoding)
- sys.stderr = PseudoOutputFile(self.console, "stderr",
- iomenu.encoding)
+ sys.stdin = StdInputFile(self.console, "stdin",
+ iomenu.encoding, iomenu.errors)
+ sys.stdout = StdOutputFile(self.console, "stdout",
+ iomenu.encoding, iomenu.errors)
+ sys.stderr = StdOutputFile(self.console, "stderr",
+ iomenu.encoding, "backslashreplace")
sys.displayhook = rpc.displayhook
# page help() text to shell.