From 112bb3ac6abc28d5e982019d667613ab168ad8d1 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 15 Mar 2011 14:55:17 -0400 Subject: Add unittests demonstrating issue #11432. --- Lib/test/test_subprocess.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 5e6c40f..92b61a9 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -3,6 +3,7 @@ from test import support import subprocess import sys import signal +import io import os import errno import tempfile @@ -1256,6 +1257,24 @@ class POSIXProcessTestCase(BaseTestCase): close_fds=False, pass_fds=(fd, ))) self.assertIn('overriding close_fds', str(context.warning)) + def test_stdout_stdin_are_single_inout_fd(self): + with io.open(os.devnull, "r+") as inout: + p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"], + stdout=inout, stdin=inout) + p.wait() + + def test_stdout_stderr_are_single_inout_fd(self): + with io.open(os.devnull, "r+") as inout: + p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"], + stdout=inout, stderr=inout) + p.wait() + + def test_stderr_stdin_are_single_inout_fd(self): + with io.open(os.devnull, "r+") as inout: + p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"], + stderr=inout, stdin=inout) + p.wait() + def test_wait_when_sigchild_ignored(self): # NOTE: sigchild_ignore.py may not be an effective test on all OSes. sigchild_ignore = support.findfile("sigchild_ignore.py", @@ -1528,19 +1547,6 @@ class ContextManagerTests(ProcessTestCase): raise c.exception -def test_main(): - unit_tests = (ProcessTestCase, - POSIXProcessTestCase, - Win32ProcessTestCase, - ProcessTestCasePOSIXPurePython, - CommandTests, - ProcessTestCaseNoPoll, - HelperFunctionTests, - CommandsWithSpaces, - ContextManagerTests) - - support.run_unittest(*unit_tests) - support.reap_children() - if __name__ == "__main__": - test_main() + unittest.main() + support.reap_children() -- cgit v0.12 From 8121898ec5a206367186bdaf7c9b13bebc28bf57 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 15 Mar 2011 14:56:39 -0400 Subject: Fix issue #11432. if the stdin pipe is the same file descriptor as either stdout or stderr in the _posixsubprocess C extension module it would unintentionally close the fds and raise an error. --- Modules/_posixsubprocess.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index 0f85da9..bf10cbb 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -99,10 +99,10 @@ static void child_exec(char *const exec_array[], if (p2cread > 2) { POSIX_CALL(close(p2cread)); } - if (c2pwrite > 2) { + if (c2pwrite > 2 && c2pwrite != p2cread) { POSIX_CALL(close(c2pwrite)); } - if (errwrite != c2pwrite && errwrite > 2) { + if (errwrite != c2pwrite && errwrite != p2cread && errwrite > 2) { POSIX_CALL(close(errwrite)); } -- cgit v0.12 From 5aaa498bad1b18864db7a6efe7ca977190187cc5 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 15 Mar 2011 14:58:08 -0400 Subject: Use start_new_session in the webbrowser module rather than a python os.setsid preeexec_fn. --- Lib/webbrowser.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Lib/webbrowser.py b/Lib/webbrowser.py index e369acb..415f12a 100644 --- a/Lib/webbrowser.py +++ b/Lib/webbrowser.py @@ -228,15 +228,9 @@ class UnixBrowser(BaseBrowser): else: # for TTY browsers, we need stdin/out inout = None - # if possible, put browser in separate process group, so - # keyboard interrupts don't affect browser as well as Python - setsid = getattr(os, 'setsid', None) - if not setsid: - setsid = getattr(os, 'setpgrp', None) - p = subprocess.Popen(cmdline, close_fds=True, stdin=inout, stdout=(self.redirect_stdout and inout or None), - stderr=inout, preexec_fn=setsid) + stderr=inout, start_new_session=True) if remote: # wait five secons. If the subprocess is not finished, the # remote invocation has (hopefully) started a new instance. -- cgit v0.12 From 1f5c958721a1f9329cb23b17cf5e36548855d72f Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Tue, 15 Mar 2011 15:04:44 -0400 Subject: - Issue #11289: `smtp.SMTP` class becomes a context manager so it can be used in a `with` statement. Contributed by Giampaolo Rodola. --- Doc/library/smtplib.rst | 14 ++++++++++++++ Lib/smtplib.py | 13 +++++++++++++ Lib/test/test_smtplib.py | 38 ++++++++++++++++++++++++++++++++++++-- Misc/NEWS | 3 +++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/Doc/library/smtplib.rst b/Doc/library/smtplib.rst index 531a64d..4805c8e 100644 --- a/Doc/library/smtplib.rst +++ b/Doc/library/smtplib.rst @@ -34,6 +34,20 @@ Protocol) and :rfc:`1869` (SMTP Service Extensions). For normal use, you should only require the initialization/connect, :meth:`sendmail`, and :meth:`quit` methods. An example is included below. + The :class:`SMTP` class supports the :keyword:`with` statement. When used + like this, the SMTP ``QUIT`` command is issued automatically when the + :keyword:`with` statement exits. E.g.:: + + >>> from smtplib import SMTP + >>> with SMTP("domain.org") as smtp: + ... smtp.noop() + ... + (250, b'Ok') + >>> + + .. versionadded:: 3.3 + Support for the :keyword:`with` statement was added. + .. class:: SMTP_SSL(host='', port=0, local_hostname=None, keyfile=None, certfile=None[, timeout]) diff --git a/Lib/smtplib.py b/Lib/smtplib.py index 14e6250..213138c 100755 --- a/Lib/smtplib.py +++ b/Lib/smtplib.py @@ -269,6 +269,19 @@ class SMTP: pass self.local_hostname = '[%s]' % addr + def __enter__(self): + return self + + def __exit__(self, *args): + try: + code, message = self.docmd("QUIT") + if code != 221: + raise SMTPResponseException(code, message) + except SMTPServerDisconnected: + pass + finally: + self.close() + def set_debuglevel(self, debuglevel): """Set the debug output level. diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index 4651f37..d973faa 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -424,6 +424,9 @@ sim_lists = {'list-1':['Mr.A@somewhere.com','Mrs.C@somewhereesle.com'], # Simulated SMTP channel & server class SimSMTPChannel(smtpd.SMTPChannel): + # For testing failures in QUIT when using the context manager API. + quit_response = None + def __init__(self, extra_features, *args, **kw): self._extrafeatures = ''.join( [ "250-{0}\r\n".format(x) for x in extra_features ]) @@ -475,19 +478,31 @@ class SimSMTPChannel(smtpd.SMTPChannel): else: self.push('550 No access for you!') + def smtp_QUIT(self, arg): + # args is ignored + if self.quit_response is None: + super(SimSMTPChannel, self).smtp_QUIT(arg) + else: + self.push(self.quit_response) + self.close_when_done() + def handle_error(self): raise class SimSMTPServer(smtpd.SMTPServer): + # For testing failures in QUIT when using the context manager API. + quit_response = None + def __init__(self, *args, **kw): self._extra_features = [] smtpd.SMTPServer.__init__(self, *args, **kw) def handle_accepted(self, conn, addr): - self._SMTPchannel = SimSMTPChannel(self._extra_features, - self, conn, addr) + self._SMTPchannel = SimSMTPChannel( + self._extra_features, self, conn, addr) + self._SMTPchannel.quit_response = self.quit_response def process_message(self, peer, mailfrom, rcpttos, data): pass @@ -620,6 +635,25 @@ class SMTPSimTests(unittest.TestCase): self.assertIn(sim_auth_credentials['cram-md5'], str(err)) smtp.close() + def test_with_statement(self): + with smtplib.SMTP(HOST, self.port) as smtp: + code, message = smtp.noop() + self.assertEqual(code, 250) + self.assertRaises(smtplib.SMTPServerDisconnected, smtp.send, b'foo') + with smtplib.SMTP(HOST, self.port) as smtp: + smtp.close() + self.assertRaises(smtplib.SMTPServerDisconnected, smtp.send, b'foo') + + def test_with_statement_QUIT_failure(self): + self.serv.quit_response = '421 QUIT FAILED' + with self.assertRaises(smtplib.SMTPResponseException) as error: + with smtplib.SMTP(HOST, self.port) as smtp: + smtp.noop() + self.assertEqual(error.exception.smtp_code, 421) + self.assertEqual(error.exception.smtp_error, b'QUIT FAILED') + # We don't need to clean up self.serv.quit_response because a new + # server is always instantiated in the setUp(). + #TODO: add tests for correct AUTH method fallback now that the #test infrastructure can support it. diff --git a/Misc/NEWS b/Misc/NEWS index 834fa46..90e402b 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -68,6 +68,9 @@ Core and Builtins Library ------- +- Issue #11289: `smtp.SMTP` class becomes a context manager so it can be used + in a `with` statement. Contributed by Giampaolo Rodola. + - Issue #11407: `TestCase.run` returns the result object used or created. Contributed by Janathan Hartley. -- cgit v0.12 From 9bb9877d8079ccfc4f5b5448ba111392140112e4 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Tue, 15 Mar 2011 20:22:50 +0100 Subject: Followup to 4c59cd84086f: add an entry in the porting guide and a "versionchanged" attribute. --- Doc/c-api/init.rst | 5 +++-- Doc/whatsnew/3.2.rst | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 2641c8b..623bd7c 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -537,10 +537,11 @@ code, or when embedding the Python interpreter: operations such as ``PyEval_ReleaseThread(tstate)``. It is not needed before calling :c:func:`PyEval_SaveThread` or :c:func:`PyEval_RestoreThread`. - .. index:: single: Py_Initialize() - This is a no-op when called for a second time. + .. versionchanged:: 3.2 + This function cannot be called before :c:func:`Py_Initialize()` anymore. + .. index:: module: _thread .. note:: diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 3829ce1..20ce228 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -2698,3 +2698,7 @@ require changes to your code: a new function, :func:`asyncore.handle_accepted`, was added to replace it. (Contributed by Giampaolo Rodola in :issue:`6706`.) + +* Due to the new :term:`GIL` implementation, :c:func:`PyEval_InitThreads()` + cannot be called before :c:func:`Py_Initialize()` anymore. + -- cgit v0.12 From 3b4652e0748a45068430bd44301a49371cd65f69 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 15 Mar 2011 15:43:39 -0400 Subject: revert the test_main() change from 08daf3ef6509 so that regrtest continues to run this properly. --- Lib/test/test_subprocess.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 92b61a9..5f158b9 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1547,6 +1547,19 @@ class ContextManagerTests(ProcessTestCase): raise c.exception +def test_main(): + unit_tests = (ProcessTestCase, + POSIXProcessTestCase, + Win32ProcessTestCase, + ProcessTestCasePOSIXPurePython, + CommandTests, + ProcessTestCaseNoPoll, + HelperFunctionTests, + CommandsWithSpaces, + ContextManagerTests) + + support.run_unittest(*unit_tests) + support.reap_children() + if __name__ == "__main__": unittest.main() - support.reap_children() -- cgit v0.12 From d6afe724cb8a701d6ad2adeb1d8d20e4fde6fca0 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Tue, 15 Mar 2011 14:44:52 -0500 Subject: improve dis test coverage (closes #11559) Thanks Matias Bordese. (a few modifications of my own) --- Lib/test/test_dis.py | 106 +++++++++++++++++++++++++++++++++++++++++++++++---- Misc/ACKS | 1 + 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py index 7a61493..c3b2a4f 100644 --- a/Lib/test/test_dis.py +++ b/Lib/test/test_dis.py @@ -1,11 +1,35 @@ # Minimal tests for dis module from test.support import run_unittest, captured_stdout +import difflib import unittest import sys import dis import io +class _C: + def __init__(self, x): + self.x = x == 1 + +dis_c_instance_method = """\ + %-4d 0 LOAD_FAST 1 (x) + 3 LOAD_CONST 1 (1) + 6 COMPARE_OP 2 (==) + 9 LOAD_FAST 0 (self) + 12 STORE_ATTR 0 (x) + 15 LOAD_CONST 0 (None) + 18 RETURN_VALUE +""" % (_C.__init__.__code__.co_firstlineno + 1,) + +dis_c_instance_method_bytes = """\ + 0 LOAD_FAST 1 (1) + 3 LOAD_CONST 1 (1) + 6 COMPARE_OP 2 (==) + 9 LOAD_FAST 0 (0) + 12 STORE_ATTR 0 (0) + 15 LOAD_CONST 0 (0) + 18 RETURN_VALUE +""" def _f(a): print(a) @@ -23,6 +47,16 @@ dis_f = """\ _f.__code__.co_firstlineno + 2) +dis_f_co_code = """\ + 0 LOAD_GLOBAL 0 (0) + 3 LOAD_FAST 0 (0) + 6 CALL_FUNCTION 1 + 9 POP_TOP + 10 LOAD_CONST 1 (1) + 13 RETURN_VALUE +""" + + def bug708901(): for res in range(1, 10): @@ -138,18 +172,27 @@ dis_compound_stmt_str = """\ """ class DisTests(unittest.TestCase): - def do_disassembly_test(self, func, expected): + + def get_disassembly(self, func, lasti=-1, wrapper=True): s = io.StringIO() save_stdout = sys.stdout sys.stdout = s - dis.dis(func) - sys.stdout = save_stdout - got = s.getvalue() + try: + if wrapper: + dis.dis(func) + else: + dis.disassemble(func, lasti) + finally: + sys.stdout = save_stdout # Trim trailing blanks (if any). - lines = got.split('\n') - lines = [line.rstrip() for line in lines] - expected = expected.split("\n") - import difflib + return [line.rstrip() for line in s.getvalue().splitlines()] + + def get_disassemble_as_string(self, func, lasti=-1): + return '\n'.join(self.get_disassembly(func, lasti, False)) + + def do_disassembly_test(self, func, expected): + lines = self.get_disassembly(func) + expected = expected.splitlines() if expected != lines: self.fail( "events did not match expectation:\n" + @@ -211,6 +254,46 @@ class DisTests(unittest.TestCase): self.do_disassembly_test(simple_stmt_str, dis_simple_stmt_str) self.do_disassembly_test(compound_stmt_str, dis_compound_stmt_str) + def test_disassemble_bytes(self): + self.do_disassembly_test(_f.__code__.co_code, dis_f_co_code) + + def test_disassemble_method(self): + self.do_disassembly_test(_C(1).__init__, dis_c_instance_method) + + def test_disassemble_method_bytes(self): + method_bytecode = _C(1).__init__.__code__.co_code + self.do_disassembly_test(method_bytecode, dis_c_instance_method_bytes) + + def test_dis_none(self): + self.assertRaises(RuntimeError, dis.dis, None) + + def test_dis_object(self): + self.assertRaises(TypeError, dis.dis, object()) + + def test_dis_traceback(self): + not_defined = object() + tb = None + old = getattr(sys, 'last_traceback', not_defined) + + def cleanup(): + if old != not_defined: + sys.last_traceback = old + else: + del sys.last_traceback + + try: + 1/0 + except Exception as e: + tb = e.__traceback__ + sys.last_traceback = tb + self.addCleanup(cleanup) + + tb_dis = self.get_disassemble_as_string(tb.tb_frame.f_code, tb.tb_lasti) + self.do_disassembly_test(None, tb_dis) + + def test_dis_object(self): + self.assertRaises(TypeError, dis.dis, object()) + code_info_code_info = """\ Name: code_info Filename: (.*) @@ -363,6 +446,13 @@ class CodeInfoTests(unittest.TestCase): dis.show_code(x) self.assertRegex(output.getvalue(), expected+"\n") + def test_code_info_object(self): + self.assertRaises(TypeError, dis.code_info, object()) + + def test_pretty_flags_no_flags(self): + self.assertEqual(dis.pretty_flags(0), '0x0') + + def test_main(): run_unittest(DisTests, CodeInfoTests) diff --git a/Misc/ACKS b/Misc/ACKS index 453f86a..d0b8951 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -100,6 +100,7 @@ David Bolen Forest Bond Gawain Bolton Gregory Bond +Matias Bordese Jurjen Bos Peter Bosch Eric Bouck -- cgit v0.12