diff options
Diffstat (limited to 'Lib')
31 files changed, 421 insertions, 74 deletions
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index d2bdc07..3d4a87a 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -775,11 +775,7 @@ class BaseEventLoop(events.AbstractEventLoop): elif self._scheduled: # Compute the desired timeout. when = self._scheduled[0]._when - deadline = max(0, when - self.time()) - if timeout is None: - timeout = deadline - else: - timeout = min(timeout, deadline) + timeout = max(0, when - self.time()) # TODO: Instrumentation only in debug mode? if logger.isEnabledFor(logging.INFO): diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 367c5fb..c7df8d8 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -87,10 +87,17 @@ class BaseSelectorEventLoop(base_events.BaseEventLoop): pass def _write_to_self(self): - try: - self._csock.send(b'x') - except (BlockingIOError, InterruptedError): - pass + # This may be called from a different thread, possibly after + # _close_self_pipe() has been called or even while it is + # running. Guard for self._csock being None or closed. When + # a socket is closed, send() raises OSError (with errno set to + # EBADF, but let's not rely on the exact error code). + csock = self._csock + if csock is not None: + try: + csock.send(b'x') + except OSError: + pass def _start_serving(self, protocol_factory, sock, sslcontext=None, server=None): diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index 27d595f..e239248 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -419,12 +419,17 @@ class StreamReader: return b'' if n < 0: - while not self._eof: - self._waiter = self._create_waiter('read') - try: - yield from self._waiter - finally: - self._waiter = None + # This used to just loop creating a new waiter hoping to + # collect everything in self._buffer, but that would + # deadlock if the subprocess sends more than self.limit + # bytes. So just call self.read(self._limit) until EOF. + blocks = [] + while True: + block = yield from self.read(self._limit) + if not block: + break + blocks.append(block) + return b''.join(blocks) else: if not self._buffer and not self._eof: self._waiter = self._create_waiter('read') diff --git a/Lib/ctypes/test/test_pep3118.py b/Lib/ctypes/test/test_pep3118.py index ad13b01..32f802c 100644 --- a/Lib/ctypes/test/test_pep3118.py +++ b/Lib/ctypes/test/test_pep3118.py @@ -96,6 +96,9 @@ class EmptyStruct(Structure): class aUnion(Union): _fields_ = [("a", c_int)] +class StructWithArrays(Structure): + _fields_ = [("x", c_long * 3 * 2), ("y", Point * 4)] + class Incomplete(Structure): pass @@ -145,10 +148,10 @@ native_types = [ ## arrays and pointers - (c_double * 4, "(4)<d", (4,), c_double), - (c_float * 4 * 3 * 2, "(2,3,4)<f", (2,3,4), c_float), - (POINTER(c_short) * 2, "(2)&<h", (2,), POINTER(c_short)), - (POINTER(c_short) * 2 * 3, "(3,2)&<h", (3,2,), POINTER(c_short)), + (c_double * 4, "<d", (4,), c_double), + (c_float * 4 * 3 * 2, "<f", (2,3,4), c_float), + (POINTER(c_short) * 2, "&<h", (2,), POINTER(c_short)), + (POINTER(c_short) * 2 * 3, "&<h", (3,2,), POINTER(c_short)), (POINTER(c_short * 2), "&(2)<h", (), POINTER(c_short)), ## structures and unions @@ -160,6 +163,9 @@ native_types = [ (EmptyStruct, "T{}", (), EmptyStruct), # the pep does't support unions (aUnion, "B", (), aUnion), + # structure with sub-arrays + (StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}", (), StructWithArrays), + (StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}", (3,), StructWithArrays), ## pointer to incomplete structure (Incomplete, "B", (), Incomplete), diff --git a/Lib/distutils/tests/support.py b/Lib/distutils/tests/support.py index 71ad4f4..7385c6b 100644 --- a/Lib/distutils/tests/support.py +++ b/Lib/distutils/tests/support.py @@ -207,4 +207,4 @@ def fixup_build_ext(cmd): cmd.library_dirs = [] else: name, equals, value = runshared.partition('=') - cmd.library_dirs = value.split(os.pathsep) + cmd.library_dirs = [d for d in value.split(os.pathsep) if d] diff --git a/Lib/email/generator.py b/Lib/email/generator.py index 1535210..4735721 100644 --- a/Lib/email/generator.py +++ b/Lib/email/generator.py @@ -51,8 +51,9 @@ class Generator: by RFC 2822. The policy keyword specifies a policy object that controls a number of - aspects of the generator's operation. The default policy maintains - backward compatibility. + aspects of the generator's operation. If no policy is specified, + the policy associated with the Message object passed to the + flatten method is used. """ self._fp = outfp @@ -76,7 +77,9 @@ class Generator: Note that for subobjects, no From_ line is printed. linesep specifies the characters used to indicate a new line in - the output. The default value is determined by the policy. + the output. The default value is determined by the policy specified + when the Generator instance was created or, if none was specified, + from the policy associated with the msg. """ # We use the _XXX constants for operating on data that comes directly diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index 7cf6a4b..84c2125 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -10,7 +10,7 @@ __all__ = ["version", "bootstrap"] _SETUPTOOLS_VERSION = "2.1" -_PIP_VERSION = "1.5.4" +_PIP_VERSION = "1.5.6" # pip currently requires ssl support, so we try to provide a nicer # error message when that is missing (http://bugs.python.org/issue19744) diff --git a/Lib/ensurepip/_bundled/pip-1.5.4-py2.py3-none-any.whl b/Lib/ensurepip/_bundled/pip-1.5.6-py2.py3-none-any.whl Binary files differindex e07d476..097ab43 100644 --- a/Lib/ensurepip/_bundled/pip-1.5.4-py2.py3-none-any.whl +++ b/Lib/ensurepip/_bundled/pip-1.5.6-py2.py3-none-any.whl diff --git a/Lib/fileinput.py b/Lib/fileinput.py index de29518..87758ad 100644 --- a/Lib/fileinput.py +++ b/Lib/fileinput.py @@ -320,7 +320,10 @@ class FileInput: self._backupfilename = 0 if self._filename == '-': self._filename = '<stdin>' - self._file = sys.stdin + if 'b' in self._mode: + self._file = sys.stdin.buffer + else: + self._file = sys.stdin self._isstdin = True else: if self._inplace: diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/EditorWindow.py index cdb6775..06fb137 100644 --- a/Lib/idlelib/EditorWindow.py +++ b/Lib/idlelib/EditorWindow.py @@ -79,6 +79,8 @@ class HelpDialog(object): self.parent = None helpDialog = HelpDialog() # singleton instance +def _Help_dialog(parent): # wrapper for htest + helpDialog.show_dialog(parent) class EditorWindow(object): @@ -1064,7 +1066,7 @@ class EditorWindow(object): try: try: mod = importlib.import_module('.' + name, package=__package__) - except ImportError: + except (ImportError, TypeError): mod = importlib.import_module(name) except ImportError: print("\nFailed to import extension: ", name) @@ -1700,19 +1702,21 @@ def fixwordbreaks(root): tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]') -def test(): - root = Tk() +def _Editor_window(parent): + root = parent fixwordbreaks(root) root.withdraw() if sys.argv[1:]: filename = sys.argv[1] else: filename = None + macosxSupport.setupApp(root, None) edit = EditorWindow(root=root, filename=filename) edit.set_close_hook(root.quit) edit.text.bind("<<close-all-windows>>", edit.close_event) - root.mainloop() - root.destroy() if __name__ == '__main__': - test() + from idlelib.idle_test.htest import run + if len(sys.argv) <= 1: + run(_Help_dialog) + run(_Editor_window) diff --git a/Lib/idlelib/aboutDialog.py b/Lib/idlelib/aboutDialog.py index 7fe1ab8..2b58013 100644 --- a/Lib/idlelib/aboutDialog.py +++ b/Lib/idlelib/aboutDialog.py @@ -12,7 +12,7 @@ class AboutDialog(Toplevel): """Modal about dialog for idle """ - def __init__(self,parent,title): + def __init__(self, parent, title): Toplevel.__init__(self, parent) self.configure(borderwidth=5) self.geometry("+%d+%d" % (parent.winfo_rootx()+30, @@ -136,10 +136,5 @@ class AboutDialog(Toplevel): self.destroy() if __name__ == '__main__': - # test the dialog - root = Tk() - def run(): - from idlelib import aboutDialog - aboutDialog.AboutDialog(root, 'About') - Button(root, text='Dialog', command=run).pack() - root.mainloop() + from idlelib.idle_test.htest import run + run(AboutDialog) diff --git a/Lib/idlelib/configSectionNameDialog.py b/Lib/idlelib/configSectionNameDialog.py index b05e38e..5137836 100644 --- a/Lib/idlelib/configSectionNameDialog.py +++ b/Lib/idlelib/configSectionNameDialog.py @@ -8,10 +8,11 @@ from tkinter import * import tkinter.messagebox as tkMessageBox class GetCfgSectionNameDialog(Toplevel): - def __init__(self, parent, title, message, used_names): + def __init__(self, parent, title, message, used_names, _htest=False): """ message - string, informational message to display used_names - string collection, names already in use for validity check + _htest - bool, change box location when running htest """ Toplevel.__init__(self, parent) self.configure(borderwidth=5) @@ -30,11 +31,12 @@ class GetCfgSectionNameDialog(Toplevel): self.messageInfo.config(width=self.frameMain.winfo_reqwidth()) self.geometry( "+%d+%d" % ( - parent.winfo_rootx() + - (parent.winfo_width()/2 - self.winfo_reqwidth()/2), - parent.winfo_rooty() + - (parent.winfo_height()/2 - self.winfo_reqheight()/2) - ) ) #centre dialog over parent + parent.winfo_rootx() + + (parent.winfo_width()/2 - self.winfo_reqwidth()/2), + parent.winfo_rooty() + + ((parent.winfo_height()/2 - self.winfo_reqheight()/2) + if not _htest else 100) + ) ) #centre dialog over parent (or below htest box) self.deiconify() #geometry set, unhide self.wait_window() @@ -92,15 +94,5 @@ if __name__ == '__main__': import unittest unittest.main('idlelib.idle_test.test_config_name', verbosity=2, exit=False) - # also human test the dialog - root = Tk() - def run(): - dlg=GetCfgSectionNameDialog(root,'Get Name', - "After the text entered with [Ok] is stripped, <nothing>, " - "'abc', or more that 30 chars are errors. " - "Close with a valid entry (printed), [Cancel], or [X]", - {'abc'}) - print(dlg.result) - Message(root, text='').pack() # will be needed for oher dialog tests - Button(root, text='Click to begin dialog test', command=run).pack() - root.mainloop() + from idlelib.idle_test.htest import run + run(GetCfgSectionNameDialog) diff --git a/Lib/idlelib/idle_test/htest.py b/Lib/idlelib/idle_test/htest.py new file mode 100644 index 0000000..001f7ee --- /dev/null +++ b/Lib/idlelib/idle_test/htest.py @@ -0,0 +1,93 @@ +'''Run human tests of Idle's window, dialog, and popup widgets. + +run(test): run *test*, a callable that causes a widget to be displayed. +runall(): run all tests defined in this file. + +Let X be a global name bound to a widget callable. End the module with + +if __name__ == '__main__': + <unittest, if there is one> + from idlelib.idle_test.htest import run + run(X) + +The X object must have a .__name__ attribute and a 'parent' parameter. +X will often be a widget class, but a callable instance with .__name__ +or a wrapper function also work. The name of wrapper functions, like +'_Editor_Window', should start with '_'. + +This file must contain a matching instance of the folling template, +with X.__name__ prepended, as in '_Editor_window_spec ...'. + +_spec = { + 'file': '', + 'kwds': {'title': ''}, + 'msg': "" + } + +file (no .py): used in runall() to import the file and get X. +kwds: passed to X (**kwds), after 'parent' is added, to initialize X. +title: an example; used for some widgets, delete if not. +msg: displayed in a master window. Hints as to how the user might + test the widget. Close the window to skip or end the test. +''' +from importlib import import_module +import tkinter as tk + + +_Editor_window_spec = { + 'file': 'EditorWindow', + 'kwds': {}, + 'msg': "Test editor functions of interest" + } + +_Help_dialog_spec = { + 'file': 'EditorWindow', + 'kwds': {}, + 'msg': "If the help text displays, this works" + } + +AboutDialog_spec = { + 'file': 'aboutDialog', + 'kwds': {'title': 'About test'}, + 'msg': "Try each button" + } + + +GetCfgSectionNameDialog_spec = { + 'file': 'configSectionNameDialog', + 'kwds': {'title':'Get Name', + 'message':'Enter something', + 'used_names': {'abc'}, + '_htest': True}, + 'msg': "After the text entered with [Ok] is stripped, <nothing>, " + "'abc', or more that 30 chars are errors.\n" + "Close 'Get Name' with a valid entry (printed to Shell), [Cancel], or [X]", + } + +def run(test): + "Display a widget with callable *test* using a _spec dict" + root = tk.Tk() + test_spec = globals()[test.__name__ + '_spec'] + test_kwds = test_spec['kwds'] + test_kwds['parent'] = root + + def run_test(): + widget = test(**test_kwds) + try: + print(widget.result) + except AttributeError: + pass + tk.Label(root, text=test_spec['msg'], justify='left').pack() + tk.Button(root, text='Test ' + test.__name__, command=run_test).pack() + root.mainloop() + +def runall(): + "Run all tests. Quick and dirty version." + for k, d in globals().items(): + if k.endswith('_spec'): + mod = import_module('idlelib.' + d['file']) + test = getattr(mod, k[:-5]) + run(test) + +if __name__ == '__main__': + runall() diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py index beaa9b3..b8836c1 100644 --- a/Lib/importlib/_bootstrap.py +++ b/Lib/importlib/_bootstrap.py @@ -1220,6 +1220,29 @@ class _SpecMethods: return self._load_unlocked() +def _fix_up_module(ns, name, pathname, cpathname=None): + # This function is used by PyImport_ExecCodeModuleObject(). + loader = ns.get('__loader__') + spec = ns.get('__spec__') + if not loader: + if spec: + loader = spec.loader + elif pathname == cpathname: + loader = SourcelessFileLoader(name, pathname) + else: + loader = SourceFileLoader(name, pathname) + if not spec: + spec = spec_from_file_location(name, pathname, loader=loader) + try: + ns['__spec__'] = spec + ns['__loader__'] = loader + ns['__file__'] = pathname + ns['__cached__'] = cpathname + except Exception: + # Not important enough to report. + pass + + # Loaders ##################################################################### class BuiltinImporter: diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 5f12832..42f48e1 100755 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -1404,6 +1404,9 @@ class _PlainTextDoc(TextDoc): def pager(text): """The first time this is called, determine what kind of pager to use.""" global pager + # Escape non-encodable characters to avoid encoding errors later + encoding = sys.getfilesystemencoding() + text = text.encode(encoding, 'backslashreplace').decode(encoding) pager = getpager() pager(text) diff --git a/Lib/random.py b/Lib/random.py index 808175a..174e755a 100644 --- a/Lib/random.py +++ b/Lib/random.py @@ -105,7 +105,9 @@ class Random(_random.Random): if a is None: try: - a = int.from_bytes(_urandom(32), 'big') + # Seed with enough bytes to span the 19937 bit + # state space for the Mersenne Twister + a = int.from_bytes(_urandom(2500), 'big') except NotImplementedError: import time a = int(time.time() * 256) # use fractional seconds diff --git a/Lib/test/script_helper.py b/Lib/test/script_helper.py index af0545b..5559349 100644 --- a/Lib/test/script_helper.py +++ b/Lib/test/script_helper.py @@ -78,7 +78,7 @@ def assert_python_failure(*args, **env_vars): """ return _assert_python(False, *args, **env_vars) -def spawn_python(*args, **kw): +def spawn_python(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw): """Run a Python subprocess with the given arguments. kw is extra keyword args to pass to subprocess.Popen. Returns a Popen @@ -86,8 +86,16 @@ def spawn_python(*args, **kw): """ cmd_line = [sys.executable, '-E'] cmd_line.extend(args) + # Under Fedora (?), GNU readline can output junk on stderr when initialized, + # depending on the TERM setting. Setting TERM=vt100 is supposed to disable + # that. References: + # - http://reinout.vanrees.org/weblog/2009/08/14/readline-invisible-character-hack.html + # - http://stackoverflow.com/questions/15760712/python-readline-module-prints-escape-character-during-import + # - http://lists.gnu.org/archive/html/bug-readline/2007-08/msg00004.html + env = kw.setdefault('env', dict(os.environ)) + env['TERM'] = 'vt100' return subprocess.Popen(cmd_line, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + stdout=stdout, stderr=stderr, **kw) def kill_python(p): diff --git a/Lib/test/test_asyncio/test_selector_events.py b/Lib/test/test_asyncio/test_selector_events.py index 964b2e8..0735237 100644 --- a/Lib/test/test_asyncio/test_selector_events.py +++ b/Lib/test/test_asyncio/test_selector_events.py @@ -121,8 +121,9 @@ class BaseSelectorEventLoopTests(unittest.TestCase): self.assertIsNone(self.loop._write_to_self()) def test_write_to_self_exception(self): - self.loop._csock.send.side_effect = OSError() - self.assertRaises(OSError, self.loop._write_to_self) + # _write_to_self() swallows OSError + self.loop._csock.send.side_effect = RuntimeError() + self.assertRaises(RuntimeError, self.loop._write_to_self) def test_sock_recv(self): sock = mock.Mock() diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 031499e..1ecc8eb 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1,7 +1,9 @@ """Tests for streams.py.""" import gc +import os import socket +import sys import unittest from unittest import mock try: @@ -583,6 +585,43 @@ class StreamReaderTests(unittest.TestCase): server.stop() self.assertEqual(msg, b"hello world!\n") + @unittest.skipIf(sys.platform == 'win32', "Don't have pipes") + def test_read_all_from_pipe_reader(self): + # See Tulip issue 168. This test is derived from the example + # subprocess_attach_read_pipe.py, but we configure the + # StreamReader's limit so that twice it is less than the size + # of the data writter. Also we must explicitly attach a child + # watcher to the event loop. + + code = """\ +import os, sys +fd = int(sys.argv[1]) +os.write(fd, b'data') +os.close(fd) +""" + rfd, wfd = os.pipe() + args = [sys.executable, '-c', code, str(wfd)] + + pipe = open(rfd, 'rb', 0) + reader = asyncio.StreamReader(loop=self.loop, limit=1) + protocol = asyncio.StreamReaderProtocol(reader, loop=self.loop) + transport, _ = self.loop.run_until_complete( + self.loop.connect_read_pipe(lambda: protocol, pipe)) + + watcher = asyncio.SafeChildWatcher() + watcher.attach_loop(self.loop) + try: + asyncio.set_child_watcher(watcher) + proc = self.loop.run_until_complete( + asyncio.create_subprocess_exec(*args, pass_fds={wfd}, loop=self.loop)) + self.loop.run_until_complete(proc.wait()) + finally: + asyncio.set_child_watcher(None) + + os.close(wfd) + data = self.loop.run_until_complete(reader.read(-1)) + self.assertEqual(data, b'data') + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_cmd_line_script.py b/Lib/test/test_cmd_line_script.py index 1e6746d..88a9e2b 100644 --- a/Lib/test/test_cmd_line_script.py +++ b/Lib/test/test_cmd_line_script.py @@ -1,5 +1,6 @@ # tests command line execution of scripts +import contextlib import importlib import importlib.machinery import zipimport @@ -8,6 +9,7 @@ import sys import os import os.path import py_compile +import subprocess import textwrap from test import support @@ -173,6 +175,53 @@ class CmdLineTest(unittest.TestCase): expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8") self.assertIn(expected, out) + @contextlib.contextmanager + def interactive_python(self, separate_stderr=False): + if separate_stderr: + p = spawn_python('-i', bufsize=1, stderr=subprocess.PIPE) + stderr = p.stderr + else: + p = spawn_python('-i', bufsize=1, stderr=subprocess.STDOUT) + stderr = p.stdout + try: + # Drain stderr until prompt + while True: + data = stderr.read(4) + if data == b">>> ": + break + stderr.readline() + yield p + finally: + kill_python(p) + stderr.close() + + def check_repl_stdout_flush(self, separate_stderr=False): + with self.interactive_python(separate_stderr) as p: + p.stdin.write(b"print('foo')\n") + p.stdin.flush() + self.assertEqual(b'foo', p.stdout.readline().strip()) + + def check_repl_stderr_flush(self, separate_stderr=False): + with self.interactive_python(separate_stderr) as p: + p.stdin.write(b"1/0\n") + p.stdin.flush() + stderr = p.stderr if separate_stderr else p.stdout + self.assertIn(b'Traceback ', stderr.readline()) + self.assertIn(b'File "<stdin>"', stderr.readline()) + self.assertIn(b'ZeroDivisionError', stderr.readline()) + + def test_repl_stdout_flush(self): + self.check_repl_stdout_flush() + + def test_repl_stdout_flush_separate_stderr(self): + self.check_repl_stdout_flush(True) + + def test_repl_stderr_flush(self): + self.check_repl_stderr_flush() + + def test_repl_stderr_flush_separate_stderr(self): + self.check_repl_stderr_flush(True) + def test_basic_script(self): with temp_dir() as script_dir: script_name = _make_test_script(script_dir, 'script') diff --git a/Lib/test/test_code_module.py b/Lib/test/test_code_module.py index 5fd21dc..7a80a80 100644 --- a/Lib/test/test_code_module.py +++ b/Lib/test/test_code_module.py @@ -51,7 +51,7 @@ class TestInteractiveConsole(unittest.TestCase): self.infunc.side_effect = ["undefined", EOFError('Finished')] self.console.interact() for call in self.stderr.method_calls: - if 'NameError:' in ''.join(call[1]): + if 'NameError' in ''.join(call[1]): break else: raise AssertionError("No syntax error from console") diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py index ebd99ed..b0fc279 100644 --- a/Lib/test/test_faulthandler.py +++ b/Lib/test/test_faulthandler.py @@ -591,6 +591,31 @@ sys.exit(exitcode) def test_register_chain(self): self.check_register(chain=True) + @contextmanager + def check_stderr_none(self): + stderr = sys.stderr + try: + sys.stderr = None + with self.assertRaises(RuntimeError) as cm: + yield + self.assertEqual(str(cm.exception), "sys.stderr is None") + finally: + sys.stderr = stderr + + def test_stderr_None(self): + # Issue #21497: provide an helpful error if sys.stderr is None, + # instead of just an attribute error: "None has no attribute fileno". + with self.check_stderr_none(): + faulthandler.enable() + with self.check_stderr_none(): + faulthandler.dump_traceback() + if hasattr(faulthandler, 'dump_traceback_later'): + with self.check_stderr_none(): + faulthandler.dump_traceback_later(1e-3) + if hasattr(faulthandler, "register"): + with self.check_stderr_none(): + faulthandler.register(signal.SIGUSR1) + if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_fileinput.py b/Lib/test/test_fileinput.py index eba55c9..1d089f5 100644 --- a/Lib/test/test_fileinput.py +++ b/Lib/test/test_fileinput.py @@ -19,11 +19,12 @@ try: except ImportError: gzip = None -from io import StringIO +from io import BytesIO, StringIO from fileinput import FileInput, hook_encoded from test.support import verbose, TESTFN, run_unittest, check_warnings from test.support import unlink as safe_unlink +from unittest import mock # The fileinput module has 2 interfaces: the FileInput class which does @@ -232,6 +233,13 @@ class FileInputTests(unittest.TestCase): finally: remove_tempfiles(t1) + def test_stdin_binary_mode(self): + with mock.patch('sys.stdin') as m_stdin: + m_stdin.buffer = BytesIO(b'spam, bacon, sausage, and spam') + fi = FileInput(files=['-'], mode='rb') + lines = list(fi) + self.assertEqual(lines, [b'spam, bacon, sausage, and spam']) + def test_file_opening_hook(self): try: # cannot use openhook and inplace mode diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 7eb104a..c0be537 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -580,6 +580,38 @@ class GCTests(unittest.TestCase): # would be damaged, with an empty __dict__. self.assertEqual(x, None) + def test_bug21435(self): + # This is a poor test - its only virtue is that it happened to + # segfault on Tim's Windows box before the patch for 21435 was + # applied. That's a nasty bug relying on specific pieces of cyclic + # trash appearing in exactly the right order in finalize_garbage()'s + # input list. + # But there's no reliable way to force that order from Python code, + # so over time chances are good this test won't really be testing much + # of anything anymore. Still, if it blows up, there's _some_ + # problem ;-) + gc.collect() + + class A: + pass + + class B: + def __init__(self, x): + self.x = x + + def __del__(self): + self.attr = None + + def do_work(): + a = A() + b = B(A()) + + a.attr = b + b.attr = a + + do_work() + gc.collect() # this blows up (bad C pointer) when it fails + @cpython_only def test_garbage_at_shutdown(self): import subprocess diff --git a/Lib/test/test_importlib/test_api.py b/Lib/test/test_importlib/test_api.py index 744001b..2a2d42b 100644 --- a/Lib/test/test_importlib/test_api.py +++ b/Lib/test/test_importlib/test_api.py @@ -241,13 +241,13 @@ class ReloadTests: '__file__': path, '__cached__': cached, '__doc__': None, - '__builtins__': __builtins__, } support.create_empty_file(path) module = self.init.import_module(name) - ns = vars(module) + ns = vars(module).copy() loader = ns.pop('__loader__') spec = ns.pop('__spec__') + ns.pop('__builtins__', None) # An implementation detail. self.assertEqual(spec.name, name) self.assertEqual(spec.loader, loader) self.assertEqual(loader.path, path) @@ -263,14 +263,14 @@ class ReloadTests: '__cached__': cached, '__path__': [os.path.dirname(init_path)], '__doc__': None, - '__builtins__': __builtins__, } os.mkdir(name) os.rename(path, init_path) reloaded = self.init.reload(module) - ns = vars(reloaded) + ns = vars(reloaded).copy() loader = ns.pop('__loader__') spec = ns.pop('__spec__') + ns.pop('__builtins__', None) # An implementation detail. self.assertEqual(spec.name, name) self.assertEqual(spec.loader, loader) self.assertIs(reloaded, module) @@ -295,10 +295,11 @@ class ReloadTests: with open(bad_path, 'w') as init_file: init_file.write('eggs = None') module = self.init.import_module(name) - ns = vars(module) + ns = vars(module).copy() loader = ns.pop('__loader__') path = ns.pop('__path__') spec = ns.pop('__spec__') + ns.pop('__builtins__', None) # An implementation detail. self.assertEqual(spec.name, name) self.assertIs(spec.loader, None) self.assertIsNot(loader, None) @@ -319,14 +320,14 @@ class ReloadTests: '__cached__': cached, '__path__': [os.path.dirname(init_path)], '__doc__': None, - '__builtins__': __builtins__, 'eggs': None, } os.rename(bad_path, init_path) reloaded = self.init.reload(module) - ns = vars(reloaded) + ns = vars(reloaded).copy() loader = ns.pop('__loader__') spec = ns.pop('__spec__') + ns.pop('__builtins__', None) # An implementation detail. self.assertEqual(spec.name, name) self.assertEqual(spec.loader, loader) self.assertIs(reloaded, module) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 267537f..ef1e056 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2615,6 +2615,38 @@ class TextIOWrapperTest(unittest.TestCase): txt.write('5') self.assertEqual(b''.join(raw._write_stack), b'123\n45') + def test_bufio_write_through(self): + # Issue #21396: write_through=True doesn't force a flush() + # on the underlying binary buffered object. + flush_called, write_called = [], [] + class BufferedWriter(self.BufferedWriter): + def flush(self, *args, **kwargs): + flush_called.append(True) + return super().flush(*args, **kwargs) + def write(self, *args, **kwargs): + write_called.append(True) + return super().write(*args, **kwargs) + + rawio = self.BytesIO() + data = b"a" + bufio = BufferedWriter(rawio, len(data)*2) + textio = self.TextIOWrapper(bufio, encoding='ascii', + write_through=True) + # write to the buffered io but don't overflow the buffer + text = data.decode('ascii') + textio.write(text) + + # buffer.flush is not called with write_through=True + self.assertFalse(flush_called) + # buffer.write *is* called with write_through=True + self.assertTrue(write_called) + self.assertEqual(rawio.getvalue(), b"") # no flush + + write_called = [] # reset + textio.write(text * 10) # total content is larger than bufio buffer + self.assertTrue(write_called) + self.assertEqual(rawio.getvalue(), data * 11) # all flushed + def test_read_nonbytes(self): # Issue #17106 # Crash when underlying read() returns non-bytes diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index 13152ec..5f14795 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -1235,6 +1235,13 @@ class LongTest(unittest.TestCase): for n in map(int, integers): self.assertEqual(n, 0) + def test_shift_bool(self): + # Issue #21422: ensure that bool << int and bool >> int return int + for value in (True, False): + for shift in (0, 2): + self.assertEqual(type(value << shift), int) + self.assertEqual(type(value >> shift), int) + def test_main(): support.run_unittest(LongTest) diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index 33ccd15..0c8a52f 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -1223,6 +1223,11 @@ class ReTests(unittest.TestCase): pat.scanner(string='abracadabra', pos=3, endpos=10).search().span(), (7, 9)) + def test_bug_20998(self): + # Issue #20998: Fullmatch of repeated single character pattern + # with ignore case. + self.assertEqual(re.fullmatch('[a-c]+', 'ABC', re.I).span(), (0, 3)) + class PatternReprTests(unittest.TestCase): def check(self, pattern, expected): diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index a6f2c64..74f74af 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -454,7 +454,7 @@ class SiginterruptTest(unittest.TestCase): stdout = first_line + stdout exitcode = process.wait() if exitcode not in (2, 3): - raise Exception("Child error (exit code %s): %s" + raise Exception("Child error (exit code %s): %r" % (exitcode, stdout)) return (exitcode == 3) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 4c8d493..32ffb5f 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -786,6 +786,7 @@ class ProcessTestCase(BaseTestCase): stdout=subprocess.PIPE, universal_newlines=1) p.stdin.write("line1\n") + p.stdin.flush() self.assertEqual(p.stdout.readline(), "line1\n") p.stdin.write("line3\n") p.stdin.close() diff --git a/Lib/urllib/robotparser.py b/Lib/urllib/robotparser.py index 978ba58..1d7b751 100644 --- a/Lib/urllib/robotparser.py +++ b/Lib/urllib/robotparser.py @@ -7,7 +7,7 @@ 2) PSF license for Python 2.2 The robots.txt Exclusion Protocol is implemented as specified in - http://info.webcrawler.com/mak/projects/robots/norobots-rfc.html + http://www.robotstxt.org/norobots-rfc.txt """ import urllib.parse, urllib.request @@ -57,7 +57,7 @@ class RobotFileParser: except urllib.error.HTTPError as err: if err.code in (401, 403): self.disallow_all = True - elif err.code >= 400: + elif err.code >= 400 and err.code < 500: self.allow_all = True else: raw = f.read() @@ -85,6 +85,7 @@ class RobotFileParser: state = 0 entry = Entry() + self.modified() for line in lines: if not line: if state == 1: @@ -129,6 +130,12 @@ class RobotFileParser: return False if self.allow_all: return True + # Until the robots.txt file has been read or found not + # to exist, we must assume that no url is allowable. + # This prevents false positives when a user erronenously + # calls can_fetch() before calling read(). + if not self.last_checked: + return False # search for given user agent matches # the first match counts parsed_url = urllib.parse.urlparse(urllib.parse.unquote(url)) |