From jython-checkins at python.org Fri Mar 1 00:06:16 2013 From: jython-checkins at python.org (alan.kennedy) Date: Fri, 1 Mar 2013 00:06:16 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=232016=3A_Fixing_the_lack_?= =?utf-8?q?of_ssl=2Emakefile?= Message-ID: <3ZH8Wr5zLlz7Ljb@mail.python.org> http://hg.python.org/jython/rev/baf84d8e91d0 changeset: 7071:baf84d8e91d0 user: Alan Kennedy date: Thu Feb 28 23:03:56 2013 +0000 summary: #2016: Fixing the lack of ssl.makefile files: Lib/socket.py | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-) diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -1856,6 +1856,9 @@ send = sendall = write + def makefile(self, mode='r', bufsize=-1): + return _fileobject(self, mode, bufsize) + def _get_server_cert(self): return self.java_ssl_socket.getSession().getPeerCertificates()[0] @@ -1869,6 +1872,9 @@ cert = self._get_server_cert() return cert.getIssuerDN().toString() + def close(self): + self.jython_socket_wrapper.close() + def test(): s = socket(AF_INET, SOCK_STREAM) s.connect(("", 80)) -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Mar 3 18:11:57 2013 From: jython-checkins at python.org (jeff.allen) Date: Sun, 3 Mar 2013 18:11:57 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Support_for_pickle_in_io_?= =?utf-8?q?=28=5Fjyio_implementation=29?= Message-ID: <3ZJrWd3JfXzNbQ@mail.python.org> http://hg.python.org/jython/rev/1b5e1afaddb4 changeset: 7072:1b5e1afaddb4 parent: 7062:a30708945630 user: Jeff Allen date: Sun Mar 03 16:49:10 2013 +0000 summary: Support for pickle in io (_jyio implementation) Added getstate/setstate to BythesIO and StringIO. (They are present in CPython _io but not in _pyio.py, therefore not free in _jyio.) Skips related to pickle now removed from test_memoryio. files: Lib/_jyio.py | 112 ++++++++++++++++++++++++- Lib/test/test_memoryio.py | 20 ---- 2 files changed, 104 insertions(+), 28 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -275,10 +275,48 @@ self._buffer = buf self._pos = 0 + # Jython: modelled after bytesio.c::bytesio_getstate def __getstate__(self): - if self.closed: - raise ValueError("__getstate__ on closed file") - return self.__dict__.copy() + d = getattr(self, '__dict__', None) + if d is not None : + d = d.copy() + return (self.getvalue(), self._pos, d) + + # Jython: modelled after bytesio.c::bytesio_setstate + def __setstate__(self, state): + + if not isinstance(state, tuple) or len(state) < 3 : + fmt = "%s.__setstate__ argument should be 3-tuple got %s" + raise TypeError( fmt % (type(self), type(state)) ) + + # Reset the object to its default state. This is only needed to handle + # the case of repeated calls to __setstate__. */ + self._buffer = bytearray() + self._pos = 0 + + # Set the value of the internal buffer. If state[0] does not support the + # buffer protocol, bytesio_write will raise the appropriate TypeError. */ + self.write(state[0]); + + # Carefully set the position value. Alternatively, we could use the seek + # method instead of modifying self._pos directly to better protect the + # object internal state against erroneous (or malicious) inputs. */ + p = state[1] + if not isinstance(p, (int, long)) : + fmt = "second item of state must be an integer, got %s" + raise TypeError( fmt % type(p) ) + elif p < 0 : + raise ValueError("position value cannot be negative") + self._pos = p + + # Set the dictionary of the instance variables. */ + d = state[2] + if not d is None : + if isinstance(d, dict) : + self.__dict__ = d + else : + fmt = "third item of state should be a dict, got %s" + raise TypeError( fmt % type(d) ) def getvalue(self): """Return the bytes value (contents) of the buffer @@ -1479,6 +1517,11 @@ """ def __init__(self, initial_value="", newline="\n"): + + # Newline mark needs to be in bytes: convert if not already so + if isinstance(newline, unicode) : + newline = newline.encode("utf-8") + super(StringIO, self).__init__(BytesIO(), encoding="utf-8", errors="strict", @@ -1487,11 +1530,64 @@ # C version, even under Windows. if newline is None: self._writetranslate = False - if initial_value: - if not isinstance(initial_value, unicode): - initial_value = unicode(initial_value) - self.write(initial_value) - self.seek(0) + # An initial value may have been supplied (and must be unicode) + if initial_value is not None: + if not isinstance(initial_value, unicode) : + fmt = "initial value should be unicode or None, got %s" + raise TypeError( fmt % type(initial_value) ) + if initial_value: + self.write(initial_value) + self.seek(0) + + # Jython: modelled after stringio.c::stringio_getstate + def __getstate__(self): + d = getattr(self, '__dict__', None) + if d is not None : + d = d.copy() + return (self.getvalue(), self._readnl, self.tell(), d) + + # Jython: modelled after stringio.c:stringio_setstate + def __setstate__(self, state): + self._checkClosed() + + if not isinstance(state, tuple) or len(state) < 4 : + fmt = "%s.__setstate__ argument should be 4-tuple got %s" + raise TypeError( fmt % (type(self), type(state)) ) + + # Initialize the object's state, but empty + self.__init__(None, state[1]) + + # Write the buffer, bypassing end-of-line translation. + value = state[0] + if value is not None: + if not isinstance(value, unicode) : + fmt = "ivalue should be unicode or None, got %s" + raise TypeError( fmt % type(value) ) + encoder = self._encoder or self._get_encoder() + b = encoder.encode(state[0]) + self.buffer.write(b) + + # Reset the object to its default state. This is only needed to handle + # the case of repeated calls to __setstate__. + self.seek(0) + + # Set the position value using seek. A long is tolerated (e.g from pickle). + p = state[2] + if not isinstance(p, (int, long)) : + fmt = "third item of state must be an integer, got %s" + raise TypeError( fmt % type(p) ) + elif p < 0 : + raise ValueError("position value cannot be negative") + self.seek(p) + + # Set the dictionary of the instance variables. */ + d = state[3] + if not d is None : + if isinstance(d, dict) : + self.__dict__ = d + else : + fmt = "fourth item of state should be a dict, got %s" + raise TypeError( fmt % type(d) ) def getvalue(self): self.flush() diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -629,11 +629,6 @@ if support.is_jython: # FIXME: Jython issue 1996 test_detach = MemoryTestMixin.test_detach - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_getstate(self): memio = self.ioclass() state = memio.__getstate__() @@ -644,11 +639,6 @@ memio.close() self.assertRaises(ValueError, memio.__getstate__) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_setstate(self): # This checks whether __setstate__ does proper input validation. memio = self.ioclass() @@ -720,11 +710,6 @@ self.assertEqual(memio.write(buf), len(buf)) self.assertEqual(memio.getvalue(), buf + buf) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_getstate(self): memio = self.ioclass() state = memio.__getstate__() @@ -736,11 +721,6 @@ memio.close() self.assertRaises(ValueError, memio.__getstate__) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_setstate(self): # This checks whether __setstate__ does proper input validation. memio = self.ioclass() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sun Mar 3 18:11:58 2013 From: jython-checkins at python.org (jeff.allen) Date: Sun, 3 Mar 2013 18:11:58 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_slice_of_pickle?= Message-ID: <3ZJrWf75KRzQqg@mail.python.org> http://hg.python.org/jython/rev/e7c373ed9da2 changeset: 7073:e7c373ed9da2 parent: 7071:baf84d8e91d0 parent: 7072:1b5e1afaddb4 user: Jeff Allen date: Sun Mar 03 17:01:25 2013 +0000 summary: Merge slice of pickle files: Lib/_jyio.py | 112 ++++++++++++++++++++++++- Lib/test/test_memoryio.py | 20 ---- 2 files changed, 104 insertions(+), 28 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -275,10 +275,48 @@ self._buffer = buf self._pos = 0 + # Jython: modelled after bytesio.c::bytesio_getstate def __getstate__(self): - if self.closed: - raise ValueError("__getstate__ on closed file") - return self.__dict__.copy() + d = getattr(self, '__dict__', None) + if d is not None : + d = d.copy() + return (self.getvalue(), self._pos, d) + + # Jython: modelled after bytesio.c::bytesio_setstate + def __setstate__(self, state): + + if not isinstance(state, tuple) or len(state) < 3 : + fmt = "%s.__setstate__ argument should be 3-tuple got %s" + raise TypeError( fmt % (type(self), type(state)) ) + + # Reset the object to its default state. This is only needed to handle + # the case of repeated calls to __setstate__. */ + self._buffer = bytearray() + self._pos = 0 + + # Set the value of the internal buffer. If state[0] does not support the + # buffer protocol, bytesio_write will raise the appropriate TypeError. */ + self.write(state[0]); + + # Carefully set the position value. Alternatively, we could use the seek + # method instead of modifying self._pos directly to better protect the + # object internal state against erroneous (or malicious) inputs. */ + p = state[1] + if not isinstance(p, (int, long)) : + fmt = "second item of state must be an integer, got %s" + raise TypeError( fmt % type(p) ) + elif p < 0 : + raise ValueError("position value cannot be negative") + self._pos = p + + # Set the dictionary of the instance variables. */ + d = state[2] + if not d is None : + if isinstance(d, dict) : + self.__dict__ = d + else : + fmt = "third item of state should be a dict, got %s" + raise TypeError( fmt % type(d) ) def getvalue(self): """Return the bytes value (contents) of the buffer @@ -1479,6 +1517,11 @@ """ def __init__(self, initial_value="", newline="\n"): + + # Newline mark needs to be in bytes: convert if not already so + if isinstance(newline, unicode) : + newline = newline.encode("utf-8") + super(StringIO, self).__init__(BytesIO(), encoding="utf-8", errors="strict", @@ -1487,11 +1530,64 @@ # C version, even under Windows. if newline is None: self._writetranslate = False - if initial_value: - if not isinstance(initial_value, unicode): - initial_value = unicode(initial_value) - self.write(initial_value) - self.seek(0) + # An initial value may have been supplied (and must be unicode) + if initial_value is not None: + if not isinstance(initial_value, unicode) : + fmt = "initial value should be unicode or None, got %s" + raise TypeError( fmt % type(initial_value) ) + if initial_value: + self.write(initial_value) + self.seek(0) + + # Jython: modelled after stringio.c::stringio_getstate + def __getstate__(self): + d = getattr(self, '__dict__', None) + if d is not None : + d = d.copy() + return (self.getvalue(), self._readnl, self.tell(), d) + + # Jython: modelled after stringio.c:stringio_setstate + def __setstate__(self, state): + self._checkClosed() + + if not isinstance(state, tuple) or len(state) < 4 : + fmt = "%s.__setstate__ argument should be 4-tuple got %s" + raise TypeError( fmt % (type(self), type(state)) ) + + # Initialize the object's state, but empty + self.__init__(None, state[1]) + + # Write the buffer, bypassing end-of-line translation. + value = state[0] + if value is not None: + if not isinstance(value, unicode) : + fmt = "ivalue should be unicode or None, got %s" + raise TypeError( fmt % type(value) ) + encoder = self._encoder or self._get_encoder() + b = encoder.encode(state[0]) + self.buffer.write(b) + + # Reset the object to its default state. This is only needed to handle + # the case of repeated calls to __setstate__. + self.seek(0) + + # Set the position value using seek. A long is tolerated (e.g from pickle). + p = state[2] + if not isinstance(p, (int, long)) : + fmt = "third item of state must be an integer, got %s" + raise TypeError( fmt % type(p) ) + elif p < 0 : + raise ValueError("position value cannot be negative") + self.seek(p) + + # Set the dictionary of the instance variables. */ + d = state[3] + if not d is None : + if isinstance(d, dict) : + self.__dict__ = d + else : + fmt = "fourth item of state should be a dict, got %s" + raise TypeError( fmt % type(d) ) def getvalue(self): self.flush() diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -629,11 +629,6 @@ if support.is_jython: # FIXME: Jython issue 1996 test_detach = MemoryTestMixin.test_detach - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_getstate(self): memio = self.ioclass() state = memio.__getstate__() @@ -644,11 +639,6 @@ memio.close() self.assertRaises(ValueError, memio.__getstate__) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_setstate(self): # This checks whether __setstate__ does proper input validation. memio = self.ioclass() @@ -720,11 +710,6 @@ self.assertEqual(memio.write(buf), len(buf)) self.assertEqual(memio.getvalue(), buf + buf) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_getstate(self): memio = self.ioclass() state = memio.__getstate__() @@ -736,11 +721,6 @@ memio.close() self.assertRaises(ValueError, memio.__getstate__) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_setstate(self): # This checks whether __setstate__ does proper input validation. memio = self.ioclass() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:41 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:41 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_lib-python_to_latest?= =?utf-8?q?_2=2E7_revision_82552=3Aa8047d1376d5?= Message-ID: <3ZPmPT4H3VzQw0@mail.python.org> http://hg.python.org/jython/rev/f763cd15ee2b changeset: 7074:f763cd15ee2b parent: 7070:e80a189574d0 user: Frank Wierzbicki date: Fri Mar 08 16:17:33 2013 -0800 summary: Update lib-python to latest 2.7 revision 82552:a8047d1376d5 files: lib-python/2.7/BaseHTTPServer.py | 6 +- lib-python/2.7/CGIHTTPServer.py | 61 +- lib-python/2.7/Cookie.py | 4 +- lib-python/2.7/HTMLParser.py | 6 +- lib-python/2.7/SocketServer.py | 22 +- lib-python/2.7/StringIO.py | 2 +- lib-python/2.7/_LWPCookieJar.py | 4 +- lib-python/2.7/__future__.py | 2 +- lib-python/2.7/_pyio.py | 20 +- lib-python/2.7/_strptime.py | 15 +- lib-python/2.7/aifc.py | 38 +- lib-python/2.7/argparse.py | 44 +- lib-python/2.7/asyncore.py | 20 +- lib-python/2.7/bdb.py | 15 +- lib-python/2.7/calendar.py | 7 +- lib-python/2.7/cgi.py | 1 - lib-python/2.7/cgitb.py | 11 +- lib-python/2.7/cmd.py | 1 + lib-python/2.7/collections.py | 187 +- lib-python/2.7/compiler/consts.py | 2 +- lib-python/2.7/compiler/pycodegen.py | 4 +- lib-python/2.7/compiler/symbols.py | 4 +- lib-python/2.7/ctypes/test/test_bitfields.py | 20 + lib-python/2.7/ctypes/test/test_numbers.py | 10 + lib-python/2.7/ctypes/test/test_returnfuncptrs.py | 30 + lib-python/2.7/ctypes/test/test_structures.py | 9 + lib-python/2.7/ctypes/test/test_win32.py | 5 +- lib-python/2.7/ctypes/util.py | 29 + lib-python/2.7/curses/__init__.py | 2 +- lib-python/2.7/decimal.py | 8 +- lib-python/2.7/distutils/__init__.py | 2 +- lib-python/2.7/distutils/ccompiler.py | 2 + lib-python/2.7/distutils/command/check.py | 3 + lib-python/2.7/distutils/config.py | 7 +- lib-python/2.7/distutils/dir_util.py | 4 + lib-python/2.7/distutils/sysconfig.py | 118 +- lib-python/2.7/distutils/tests/test_build_ext.py | 5 +- lib-python/2.7/distutils/tests/test_dir_util.py | 18 + lib-python/2.7/distutils/tests/test_msvc9compiler.py | 2 +- lib-python/2.7/distutils/tests/test_register.py | 33 +- lib-python/2.7/distutils/tests/test_sdist.py | 7 +- lib-python/2.7/distutils/tests/test_sysconfig.py | 29 + lib-python/2.7/distutils/unixccompiler.py | 70 +- lib-python/2.7/distutils/util.py | 96 +- lib-python/2.7/doctest.py | 17 +- lib-python/2.7/email/_parseaddr.py | 8 +- lib-python/2.7/email/base64mime.py | 2 +- lib-python/2.7/email/feedparser.py | 4 +- lib-python/2.7/email/generator.py | 12 +- lib-python/2.7/email/test/test_email.py | 29 + lib-python/2.7/email/test/test_email_renamed.py | 32 + lib-python/2.7/email/utils.py | 4 +- lib-python/2.7/ftplib.py | 11 +- lib-python/2.7/glob.py | 25 +- lib-python/2.7/gzip.py | 81 +- lib-python/2.7/hashlib.py | 2 +- lib-python/2.7/heapq.py | 84 +- lib-python/2.7/httplib.py | 25 +- lib-python/2.7/idlelib/CallTips.py | 33 +- lib-python/2.7/idlelib/ColorDelegator.py | 9 +- lib-python/2.7/idlelib/EditorWindow.py | 56 +- lib-python/2.7/idlelib/FormatParagraph.py | 3 +- lib-python/2.7/idlelib/HyperParser.py | 5 + lib-python/2.7/idlelib/IOBinding.py | 37 +- lib-python/2.7/idlelib/NEWS.txt | 35 +- lib-python/2.7/idlelib/OutputWindow.py | 6 +- lib-python/2.7/idlelib/PyShell.py | 130 +- lib-python/2.7/idlelib/ReplaceDialog.py | 30 +- lib-python/2.7/idlelib/config-extensions.def | 2 + lib-python/2.7/idlelib/configDialog.py | 18 +- lib-python/2.7/idlelib/configHandler.py | 53 +- lib-python/2.7/idlelib/help.txt | 24 +- lib-python/2.7/idlelib/idlever.py | 2 +- lib-python/2.7/idlelib/macosxSupport.py | 16 +- lib-python/2.7/idlelib/run.py | 22 +- lib-python/2.7/io.py | 11 +- lib-python/2.7/json/__init__.py | 50 +- lib-python/2.7/json/decoder.py | 17 +- lib-python/2.7/json/encoder.py | 17 +- lib-python/2.7/json/tests/test_decode.py | 9 + lib-python/2.7/json/tests/test_dump.py | 9 + lib-python/2.7/json/tests/test_fail.py | 24 +- lib-python/2.7/json/tests/test_float.py | 15 + lib-python/2.7/json/tests/test_pass1.py | 20 +- lib-python/2.7/json/tool.py | 17 +- lib-python/2.7/lib-tk/Tkinter.py | 74 +- lib-python/2.7/lib-tk/test/test_ttk/test_functions.py | 40 +- lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py | 8 + lib-python/2.7/lib-tk/tkSimpleDialog.py | 2 +- lib-python/2.7/lib-tk/ttk.py | 126 +- lib-python/2.7/lib2to3/fixer_util.py | 16 +- lib-python/2.7/lib2to3/pgen2/driver.py | 17 + lib-python/2.7/lib2to3/refactor.py | 2 +- lib-python/2.7/lib2to3/tests/test_fixers.py | 12 + lib-python/2.7/locale.py | 10 +- lib-python/2.7/logging/__init__.py | 17 +- lib-python/2.7/logging/handlers.py | 72 +- lib-python/2.7/mailbox.py | 76 +- lib-python/2.7/mimetypes.py | 3 +- lib-python/2.7/multiprocessing/connection.py | 11 +- lib-python/2.7/multiprocessing/dummy/__init__.py | 3 +- lib-python/2.7/multiprocessing/forking.py | 20 +- lib-python/2.7/multiprocessing/pool.py | 35 +- lib-python/2.7/multiprocessing/process.py | 6 +- lib-python/2.7/multiprocessing/util.py | 39 +- lib-python/2.7/plat-generic/regen | 2 +- lib-python/2.7/platform.py | 37 +- lib-python/2.7/posixpath.py | 96 +- lib-python/2.7/pstats.py | 10 +- lib-python/2.7/py_compile.py | 2 +- lib-python/2.7/pyclbr.py | 2 + lib-python/2.7/pydoc.py | 7 +- lib-python/2.7/random.py | 20 +- lib-python/2.7/rfc822.py | 2 +- lib-python/2.7/rlcompleter.py | 36 +- lib-python/2.7/runpy.py | 2 +- lib-python/2.7/shutil.py | 8 +- lib-python/2.7/smtplib.py | 4 +- lib-python/2.7/socket.py | 4 +- lib-python/2.7/sqlite3/dbapi2.py | 4 +- lib-python/2.7/sqlite3/dump.py | 9 +- lib-python/2.7/sqlite3/test/dump.py | 23 + lib-python/2.7/sqlite3/test/hooks.py | 19 + lib-python/2.7/sqlite3/test/regression.py | 28 +- lib-python/2.7/sqlite3/test/userfunctions.py | 60 +- lib-python/2.7/sre_compile.py | 1 + lib-python/2.7/sre_constants.py | 4 +- lib-python/2.7/sre_parse.py | 19 +- lib-python/2.7/ssl.py | 20 +- lib-python/2.7/string.py | 8 +- lib-python/2.7/subprocess.py | 55 +- lib-python/2.7/sysconfig.py | 158 +-- lib-python/2.7/tarfile.py | 16 +- lib-python/2.7/telnetlib.py | 130 ++ lib-python/2.7/tempfile.py | 43 +- lib-python/2.7/test/keycert.pem | 59 +- lib-python/2.7/test/pickletester.py | 35 +- lib-python/2.7/test/regrtest.py | 8 +- lib-python/2.7/test/script_helper.py | 8 +- lib-python/2.7/test/sha256.pem | 233 ++-- lib-python/2.7/test/string_tests.py | 18 + lib-python/2.7/test/subprocessdata/sigchild_ignore.py | 11 +- lib-python/2.7/test/test_StringIO.py | 42 + lib-python/2.7/test/test_aifc.py | 7 + lib-python/2.7/test/test_argparse.py | 137 ++- lib-python/2.7/test/test_array.py | 13 + lib-python/2.7/test/test_ast.py | 6 + lib-python/2.7/test/test_asyncore.py | 27 +- lib-python/2.7/test/test_audioop.py | 405 +++++-- lib-python/2.7/test/test_bigmem.py | 8 +- lib-python/2.7/test/test_bisect.py | 53 +- lib-python/2.7/test/test_builtin.py | 6 +- lib-python/2.7/test/test_bytes.py | 21 + lib-python/2.7/test/test_bz2.py | 81 +- lib-python/2.7/test/test_calendar.py | 26 +- lib-python/2.7/test/test_capi.py | 56 +- lib-python/2.7/test/test_cmd.py | 8 +- lib-python/2.7/test/test_cmd_line.py | 38 +- lib-python/2.7/test/test_cmd_line_script.py | 18 +- lib-python/2.7/test/test_codeccallbacks.py | 6 +- lib-python/2.7/test/test_codecs.py | 410 +++++++- lib-python/2.7/test/test_codeop.py | 2 +- lib-python/2.7/test/test_compile.py | 28 + lib-python/2.7/test/test_cookie.py | 15 +- lib-python/2.7/test/test_cpickle.py | 17 +- lib-python/2.7/test/test_csv.py | 9 + lib-python/2.7/test/test_decimal.py | 12 + lib-python/2.7/test/test_deque.py | 16 + lib-python/2.7/test/test_descr.py | 26 +- lib-python/2.7/test/test_dict.py | 8 + lib-python/2.7/test/test_dictcomps.py | 117 +- lib-python/2.7/test/test_doctest.py | 29 +- lib-python/2.7/test/test_docxmlrpc.py | 2 +- lib-python/2.7/test/test_email.py | 2 + lib-python/2.7/test/test_exceptions.py | 12 + lib-python/2.7/test/test_fcntl.py | 21 + lib-python/2.7/test/test_file2k.py | 147 ++- lib-python/2.7/test/test_fileio.py | 49 +- lib-python/2.7/test/test_format.py | 10 + lib-python/2.7/test/test_functools.py | 21 +- lib-python/2.7/test/test_gc.py | 69 + lib-python/2.7/test/test_gdb.py | 75 +- lib-python/2.7/test/test_generators.py | 3 +- lib-python/2.7/test/test_genexps.py | 3 +- lib-python/2.7/test/test_glob.py | 115 +- lib-python/2.7/test/test_gzip.py | 25 + lib-python/2.7/test/test_hashlib.py | 50 +- lib-python/2.7/test/test_heapq.py | 26 + lib-python/2.7/test/test_htmlparser.py | 10 + lib-python/2.7/test/test_httplib.py | 69 +- lib-python/2.7/test/test_httpservers.py | 76 +- lib-python/2.7/test/test_imaplib.py | 2 +- lib-python/2.7/test/test_import.py | 103 +- lib-python/2.7/test/test_int.py | 63 +- lib-python/2.7/test/test_io.py | 165 ++- lib-python/2.7/test/test_iter.py | 15 + lib-python/2.7/test/test_itertools.py | 6 + lib-python/2.7/test/test_logging.py | 49 +- lib-python/2.7/test/test_long.py | 6 +- lib-python/2.7/test/test_mailbox.py | 208 ++- lib-python/2.7/test/test_marshal.py | 51 +- lib-python/2.7/test/test_memoryio.py | 16 +- lib-python/2.7/test/test_minidom.py | 2 +- lib-python/2.7/test/test_mmap.py | 20 + lib-python/2.7/test/test_multiprocessing.py | 173 +++- lib-python/2.7/test/test_mutex.py | 2 +- lib-python/2.7/test/test_old_mailbox.py | 16 +- lib-python/2.7/test/test_optparse.py | 7 + lib-python/2.7/test/test_os.py | 16 +- lib-python/2.7/test/test_parser.py | 61 +- lib-python/2.7/test/test_pdb.py | 61 +- lib-python/2.7/test/test_peepholer.py | 13 +- lib-python/2.7/test/test_pickle.py | 20 +- lib-python/2.7/test/test_poll.py | 10 + lib-python/2.7/test/test_posix.py | 133 +- lib-python/2.7/test/test_posixpath.py | 103 +- lib-python/2.7/test/test_property.py | 4 +- lib-python/2.7/test/test_pty.py | 2 +- lib-python/2.7/test/test_pwd.py | 9 + lib-python/2.7/test/test_pyclbr.py | 5 + lib-python/2.7/test/test_pydoc.py | 43 +- lib-python/2.7/test/test_pyexpat.py | 55 +- lib-python/2.7/test/test_random.py | 56 +- lib-python/2.7/test/test_re.py | 94 + lib-python/2.7/test/test_readline.py | 4 + lib-python/2.7/test/test_resource.py | 17 + lib-python/2.7/test/test_sax.py | 166 ++- lib-python/2.7/test/test_select.py | 9 + lib-python/2.7/test/test_shutil.py | 30 + lib-python/2.7/test/test_signal.py | 16 +- lib-python/2.7/test/test_socket.py | 111 +- lib-python/2.7/test/test_socketserver.py | 41 +- lib-python/2.7/test/test_ssl.py | 50 +- lib-python/2.7/test/test_str.py | 27 + lib-python/2.7/test/test_strptime.py | 8 + lib-python/2.7/test/test_struct.py | 26 +- lib-python/2.7/test/test_subprocess.py | 82 + lib-python/2.7/test/test_support.py | 152 ++- lib-python/2.7/test/test_sys.py | 207 +-- lib-python/2.7/test/test_sys_settrace.py | 13 +- lib-python/2.7/test/test_sysconfig.py | 9 + lib-python/2.7/test/test_tarfile.py | 32 +- lib-python/2.7/test/test_tcl.py | 22 + lib-python/2.7/test/test_telnetlib.py | 92 +- lib-python/2.7/test/test_tempfile.py | 87 +- lib-python/2.7/test/test_textwrap.py | 62 +- lib-python/2.7/test/test_thread.py | 23 + lib-python/2.7/test/test_threading.py | 29 + lib-python/2.7/test/test_time.py | 2 +- lib-python/2.7/test/test_tokenize.py | 29 + lib-python/2.7/test/test_tools.py | 339 ++++++- lib-python/2.7/test/test_ucn.py | 23 + lib-python/2.7/test/test_unicode.py | 27 + lib-python/2.7/test/test_urllib.py | 21 + lib-python/2.7/test/test_urllib2.py | 58 +- lib-python/2.7/test/test_urllib2_localnet.py | 8 + lib-python/2.7/test/test_urllib2net.py | 4 +- lib-python/2.7/test/test_urlparse.py | 49 + lib-python/2.7/test/test_uu.py | 4 +- lib-python/2.7/test/test_weakref.py | 101 +- lib-python/2.7/test/test_winreg.py | 45 +- lib-python/2.7/test/test_winsound.py | 1 + lib-python/2.7/test/test_wsgiref.py | 108 +- lib-python/2.7/test/test_xml_etree.py | 20 + lib-python/2.7/test/test_xrange.py | 75 + lib-python/2.7/test/test_zipfile.py | 141 ++- lib-python/2.7/test/test_zipimport_support.py | 26 +- lib-python/2.7/test/test_zlib.py | 37 + lib-python/2.7/test/testtar.tar | Bin lib-python/2.7/textwrap.py | 12 +- lib-python/2.7/threading.py | 7 +- lib-python/2.7/tokenize.py | 14 +- lib-python/2.7/traceback.py | 2 +- lib-python/2.7/unittest/case.py | 17 +- lib-python/2.7/unittest/main.py | 5 +- lib-python/2.7/unittest/runner.py | 2 +- lib-python/2.7/unittest/signals.py | 16 +- lib-python/2.7/unittest/test/test_break.py | 32 + lib-python/2.7/unittest/test/test_discovery.py | 14 + lib-python/2.7/unittest/test/test_runner.py | 13 + lib-python/2.7/unittest/test/test_skipping.py | 30 + lib-python/2.7/urllib2.py | 11 +- lib-python/2.7/urlparse.py | 33 +- lib-python/2.7/wave.py | 12 +- lib-python/2.7/wsgiref/handlers.py | 12 +- lib-python/2.7/wsgiref/validate.py | 4 +- lib-python/2.7/xml/dom/minidom.py | 5 +- lib-python/2.7/xml/etree/ElementTree.py | 7 +- lib-python/2.7/xml/sax/_exceptions.py | 6 +- lib-python/2.7/xml/sax/expatreader.py | 5 +- lib-python/2.7/xml/sax/saxutils.py | 114 +- lib-python/2.7/xml/sax/xmlreader.py | 2 +- lib-python/2.7/xmlrpclib.py | 2 +- lib-python/2.7/zipfile.py | 551 +++++---- 294 files changed, 8597 insertions(+), 2566 deletions(-) diff --git a/lib-python/2.7/BaseHTTPServer.py b/lib-python/2.7/BaseHTTPServer.py --- a/lib-python/2.7/BaseHTTPServer.py +++ b/lib-python/2.7/BaseHTTPServer.py @@ -447,13 +447,13 @@ specified as subsequent arguments (it's just like printf!). - The client host and current date/time are prefixed to - every message. + The client ip address and current date/time are prefixed to every + message. """ sys.stderr.write("%s - - [%s] %s\n" % - (self.address_string(), + (self.client_address[0], self.log_date_time_string(), format%args)) diff --git a/lib-python/2.7/CGIHTTPServer.py b/lib-python/2.7/CGIHTTPServer.py --- a/lib-python/2.7/CGIHTTPServer.py +++ b/lib-python/2.7/CGIHTTPServer.py @@ -84,9 +84,11 @@ path begins with one of the strings in self.cgi_directories (and the next character is a '/' or the end of the string). """ - splitpath = _url_collapse_path_split(self.path) - if splitpath[0] in self.cgi_directories: - self.cgi_info = splitpath + collapsed_path = _url_collapse_path(self.path) + dir_sep = collapsed_path.find('/', 1) + head, tail = collapsed_path[:dir_sep], collapsed_path[dir_sep+1:] + if head in self.cgi_directories: + self.cgi_info = head, tail return True return False @@ -298,51 +300,46 @@ self.log_message("CGI script exited OK") -# TODO(gregory.p.smith): Move this into an appropriate library. -def _url_collapse_path_split(path): +def _url_collapse_path(path): """ Given a URL path, remove extra '/'s and '.' path elements and collapse - any '..' references. + any '..' references and returns a colllapsed path. Implements something akin to RFC-2396 5.2 step 6 to parse relative paths. + The utility of this function is limited to is_cgi method and helps + preventing some security attacks. Returns: A tuple of (head, tail) where tail is everything after the final / and head is everything before it. Head will always start with a '/' and, if it contains anything else, never have a trailing '/'. Raises: IndexError if too many '..' occur within the path. + """ # Similar to os.path.split(os.path.normpath(path)) but specific to URL # path semantics rather than local operating system semantics. - path_parts = [] - for part in path.split('/'): - if part == '.': - path_parts.append('') - else: - path_parts.append(part) - # Filter out blank non trailing parts before consuming the '..'. - path_parts = [part for part in path_parts[:-1] if part] + path_parts[-1:] + path_parts = path.split('/') + head_parts = [] + for part in path_parts[:-1]: + if part == '..': + head_parts.pop() # IndexError if more '..' than prior parts + elif part and part != '.': + head_parts.append( part ) if path_parts: - # Special case for CGI's for PATH_INFO - if path.startswith('/cgi-bin') or path.startswith('/htbin'): - tail_part = [] - while path_parts[-1] not in ('cgi-bin','htbin'): - tail_part.insert(0,path_parts.pop()) - tail_part = "/".join(tail_part) - else: - tail_part = path_parts.pop() + tail_part = path_parts.pop() + if tail_part: + if tail_part == '..': + head_parts.pop() + tail_part = '' + elif tail_part == '.': + tail_part = '' else: tail_part = '' - head_parts = [] - for part in path_parts: - if part == '..': - head_parts.pop() - else: - head_parts.append(part) - if tail_part and tail_part == '..': - head_parts.pop() - tail_part = '' - return ('/' + '/'.join(head_parts), tail_part) + + splitpath = ('/' + '/'.join(head_parts), tail_part) + collapsed_path = "/".join(splitpath) + + return collapsed_path nobody = None diff --git a/lib-python/2.7/Cookie.py b/lib-python/2.7/Cookie.py --- a/lib-python/2.7/Cookie.py +++ b/lib-python/2.7/Cookie.py @@ -390,7 +390,7 @@ from time import gmtime, time now = time() year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future) - return "%s, %02d-%3s-%4d %02d:%02d:%02d GMT" % \ + return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \ (weekdayname[wd], day, monthname[month], year, hh, mm, ss) @@ -539,7 +539,7 @@ r"(?P" # Start of group 'val' r'"(?:[^\\"]|\\.)*"' # Any doublequoted string r"|" # or - r"\w{3},\s[\w\d-]{9,11}\s[\d:]{8}\sGMT" # Special case for "expires" attr + r"\w{3},\s[\s\w\d-]{9,11}\s[\d:]{8}\sGMT" # Special case for "expires" attr r"|" # or ""+ _LegalCharsPatt +"*" # Any word or empty string r")" # End of group 'val' diff --git a/lib-python/2.7/HTMLParser.py b/lib-python/2.7/HTMLParser.py --- a/lib-python/2.7/HTMLParser.py +++ b/lib-python/2.7/HTMLParser.py @@ -22,13 +22,13 @@ starttagopen = re.compile('<[a-zA-Z]') piclose = re.compile('>') commentclose = re.compile(r'--\s*>') -tagfind = re.compile('[a-zA-Z][-.a-zA-Z0-9:_]*') +tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*') # see http://www.w3.org/TR/html5/tokenization.html#tag-open-state # and http://www.w3.org/TR/html5/tokenization.html#tag-name-state tagfind_tolerant = re.compile('[a-zA-Z][^\t\n\r\f />\x00]*') attrfind = re.compile( - r'[\s/]*((?<=[\'"\s/])[^\s/>][^\s/=>]*)(\s*=+\s*' + r'((?<=[\'"\s/])[^\s/>][^\s/=>]*)(\s*=+\s*' r'(\'[^\']*\'|"[^"]*"|(?![\'"])[^>\s]*))?(?:\s|/(?!>))*') locatestarttagend = re.compile(r""" @@ -289,7 +289,7 @@ match = tagfind.match(rawdata, i+1) assert match, 'unexpected call to parse_starttag()' k = match.end() - self.lasttag = tag = rawdata[i+1:k].lower() + self.lasttag = tag = match.group(1).lower() while k < endpos: m = attrfind.match(rawdata, k) diff --git a/lib-python/2.7/SocketServer.py b/lib-python/2.7/SocketServer.py --- a/lib-python/2.7/SocketServer.py +++ b/lib-python/2.7/SocketServer.py @@ -133,6 +133,7 @@ import select import sys import os +import errno try: import threading except ImportError: @@ -147,6 +148,15 @@ "ThreadingUnixStreamServer", "ThreadingUnixDatagramServer"]) +def _eintr_retry(func, *args): + """restart a system call interrupted by EINTR""" + while True: + try: + return func(*args) + except (OSError, select.error) as e: + if e.args[0] != errno.EINTR: + raise + class BaseServer: """Base class for server classes. @@ -222,7 +232,8 @@ # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. - r, w, e = select.select([self], [], [], poll_interval) + r, w, e = _eintr_retry(select.select, [self], [], [], + poll_interval) if self in r: self._handle_request_noblock() finally: @@ -262,7 +273,7 @@ timeout = self.timeout elif self.timeout is not None: timeout = min(timeout, self.timeout) - fd_sets = select.select([self], [], [], timeout) + fd_sets = _eintr_retry(select.select, [self], [], [], timeout) if not fd_sets[0]: self.handle_timeout() return @@ -690,7 +701,12 @@ def finish(self): if not self.wfile.closed: - self.wfile.flush() + try: + self.wfile.flush() + except socket.error: + # An final socket error may have occurred here, such as + # the local error ECONNABORTED. + pass self.wfile.close() self.rfile.close() diff --git a/lib-python/2.7/StringIO.py b/lib-python/2.7/StringIO.py --- a/lib-python/2.7/StringIO.py +++ b/lib-python/2.7/StringIO.py @@ -158,7 +158,7 @@ newpos = self.len else: newpos = i+1 - if length is not None and length > 0: + if length is not None and length >= 0: if self.pos + length < newpos: newpos = self.pos + length r = self.buf[self.pos:newpos] diff --git a/lib-python/2.7/_LWPCookieJar.py b/lib-python/2.7/_LWPCookieJar.py --- a/lib-python/2.7/_LWPCookieJar.py +++ b/lib-python/2.7/_LWPCookieJar.py @@ -48,7 +48,7 @@ class LWPCookieJar(FileCookieJar): """ - The LWPCookieJar saves a sequence of"Set-Cookie3" lines. + The LWPCookieJar saves a sequence of "Set-Cookie3" lines. "Set-Cookie3" is the format used by the libwww-perl libary, not known to be compatible with any browser, but which is easy to read and doesn't lose information about RFC 2965 cookies. @@ -60,7 +60,7 @@ """ def as_lwp_str(self, ignore_discard=True, ignore_expires=True): - """Return cookies as a string of "\n"-separated "Set-Cookie3" headers. + """Return cookies as a string of "\\n"-separated "Set-Cookie3" headers. ignore_discard and ignore_expires: see docstring for FileCookieJar.save diff --git a/lib-python/2.7/__future__.py b/lib-python/2.7/__future__.py --- a/lib-python/2.7/__future__.py +++ b/lib-python/2.7/__future__.py @@ -112,7 +112,7 @@ CO_FUTURE_DIVISION) absolute_import = _Feature((2, 5, 0, "alpha", 1), - (2, 7, 0, "alpha", 0), + (3, 0, 0, "alpha", 0), CO_FUTURE_ABSOLUTE_IMPORT) with_statement = _Feature((2, 5, 0, "alpha", 1), diff --git a/lib-python/2.7/_pyio.py b/lib-python/2.7/_pyio.py --- a/lib-python/2.7/_pyio.py +++ b/lib-python/2.7/_pyio.py @@ -340,8 +340,10 @@ This method has no effect if the file is already closed. """ if not self.__closed: - self.flush() - self.__closed = True + try: + self.flush() + finally: + self.__closed = True def __del__(self): """Destructor. Calls close().""" @@ -883,12 +885,18 @@ return pos def readable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return True def writable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return True def seekable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return True @@ -1546,6 +1554,8 @@ return self._buffer def seekable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return self._seekable def readable(self): @@ -1560,8 +1570,10 @@ def close(self): if self.buffer is not None and not self.closed: - self.flush() - self.buffer.close() + try: + self.flush() + finally: + self.buffer.close() @property def closed(self): diff --git a/lib-python/2.7/_strptime.py b/lib-python/2.7/_strptime.py --- a/lib-python/2.7/_strptime.py +++ b/lib-python/2.7/_strptime.py @@ -326,7 +326,8 @@ if len(data_string) != found.end(): raise ValueError("unconverted data remains: %s" % data_string[found.end():]) - year = 1900 + + year = None month = day = 1 hour = minute = second = fraction = 0 tz = -1 @@ -425,6 +426,12 @@ else: tz = value break + leap_year_fix = False + if year is None and month == 2 and day == 29: + year = 1904 # 1904 is first leap year of 20th century + leap_year_fix = True + elif year is None: + year = 1900 # If we know the week of the year and what day of that week, we can figure # out the Julian day of the year. if julian == -1 and week_of_year != -1 and weekday != -1: @@ -446,6 +453,12 @@ day = datetime_result.day if weekday == -1: weekday = datetime_date(year, month, day).weekday() + if leap_year_fix: + # the caller didn't supply a year but asked for Feb 29th. We couldn't + # use the default of 1900 for computations. We set it back to ensure + # that February 29th is smaller than March 1st. + year = 1900 + return (time.struct_time((year, month, day, hour, minute, second, weekday, julian, tz)), fraction) diff --git a/lib-python/2.7/aifc.py b/lib-python/2.7/aifc.py --- a/lib-python/2.7/aifc.py +++ b/lib-python/2.7/aifc.py @@ -732,22 +732,28 @@ self._patchheader() def close(self): - self._ensure_header_written(0) - if self._datawritten & 1: - # quick pad to even size - self._file.write(chr(0)) - self._datawritten = self._datawritten + 1 - self._writemarkers() - if self._nframeswritten != self._nframes or \ - self._datalength != self._datawritten or \ - self._marklength: - self._patchheader() - if self._comp: - self._comp.CloseCompressor() - self._comp = None - # Prevent ref cycles - self._convert = None - self._file.close() + if self._file is None: + return + try: + self._ensure_header_written(0) + if self._datawritten & 1: + # quick pad to even size + self._file.write(chr(0)) + self._datawritten = self._datawritten + 1 + self._writemarkers() + if self._nframeswritten != self._nframes or \ + self._datalength != self._datawritten or \ + self._marklength: + self._patchheader() + if self._comp: + self._comp.CloseCompressor() + self._comp = None + finally: + # Prevent ref cycles + self._convert = None + f = self._file + self._file = None + f.close() # # Internal methods. diff --git a/lib-python/2.7/argparse.py b/lib-python/2.7/argparse.py --- a/lib-python/2.7/argparse.py +++ b/lib-python/2.7/argparse.py @@ -740,10 +740,10 @@ - default -- The value to be produced if the option is not specified. - - type -- The type which the command-line arguments should be converted - to, should be one of 'string', 'int', 'float', 'complex' or a - callable object that accepts a single string argument. If None, - 'string' is assumed. + - type -- A callable that accepts a single string argument, and + returns the converted value. The standard Python types str, int, + float, and complex are useful examples of such callables. If None, + str is used. - choices -- A container of values that should be allowed. If not None, after a command-line argument has been converted to the appropriate @@ -1692,9 +1692,12 @@ return args def parse_known_args(self, args=None, namespace=None): - # args default to the system args if args is None: + # args default to the system args args = _sys.argv[1:] + else: + # make sure that args are mutable + args = list(args) # default Namespace built from parser defaults if namespace is None: @@ -1705,10 +1708,7 @@ if action.dest is not SUPPRESS: if not hasattr(namespace, action.dest): if action.default is not SUPPRESS: - default = action.default - if isinstance(action.default, basestring): - default = self._get_value(action, default) - setattr(namespace, action.dest, default) + setattr(namespace, action.dest, action.default) # add any parser defaults that aren't present for dest in self._defaults: @@ -1936,12 +1936,23 @@ if positionals: self.error(_('too few arguments')) - # make sure all required actions were present + # make sure all required actions were present, and convert defaults. for action in self._actions: - if action.required: - if action not in seen_actions: + if action not in seen_actions: + if action.required: name = _get_action_name(action) self.error(_('argument %s is required') % name) + else: + # Convert action default now instead of doing it before + # parsing arguments to avoid calling convert functions + # twice (which may fail) if the argument was given, but + # only if it was defined already in the namespace + if (action.default is not None and + isinstance(action.default, basestring) and + hasattr(namespace, action.dest) and + action.default is getattr(namespace, action.dest)): + setattr(namespace, action.dest, + self._get_value(action, action.default)) # make sure all required groups had one option present for group in self._mutually_exclusive_groups: @@ -1967,7 +1978,7 @@ for arg_string in arg_strings: # for regular arguments, just add them back into the list - if arg_string[0] not in self.fromfile_prefix_chars: + if not arg_string or arg_string[0] not in self.fromfile_prefix_chars: new_arg_strings.append(arg_string) # replace arguments referencing files with the file content @@ -2174,9 +2185,12 @@ # Value conversion methods # ======================== def _get_values(self, action, arg_strings): - # for everything but PARSER args, strip out '--' + # for everything but PARSER, REMAINDER args, strip out first '--' if action.nargs not in [PARSER, REMAINDER]: - arg_strings = [s for s in arg_strings if s != '--'] + try: + arg_strings.remove('--') + except ValueError: + pass # optional argument produces a default when not present if not arg_strings and action.nargs == OPTIONAL: diff --git a/lib-python/2.7/asyncore.py b/lib-python/2.7/asyncore.py --- a/lib-python/2.7/asyncore.py +++ b/lib-python/2.7/asyncore.py @@ -225,6 +225,7 @@ debug = False connected = False accepting = False + connecting = False closing = False addr = None ignore_log_types = frozenset(['warning']) @@ -248,7 +249,7 @@ try: self.addr = sock.getpeername() except socket.error, err: - if err.args[0] == ENOTCONN: + if err.args[0] in (ENOTCONN, EINVAL): # To handle the case where we got an unconnected # socket. self.connected = False @@ -342,9 +343,11 @@ def connect(self, address): self.connected = False + self.connecting = True err = self.socket.connect_ex(address) if err in (EINPROGRESS, EALREADY, EWOULDBLOCK) \ or err == EINVAL and os.name in ('nt', 'ce'): + self.addr = address return if err in (0, EISCONN): self.addr = address @@ -390,7 +393,7 @@ else: return data except socket.error, why: - # winsock sometimes throws ENOTCONN + # winsock sometimes raises ENOTCONN if why.args[0] in _DISCONNECTED: self.handle_close() return '' @@ -400,6 +403,7 @@ def close(self): self.connected = False self.accepting = False + self.connecting = False self.del_channel() try: self.socket.close() @@ -438,7 +442,8 @@ # sockets that are connected self.handle_accept() elif not self.connected: - self.handle_connect_event() + if self.connecting: + self.handle_connect_event() self.handle_read() else: self.handle_read() @@ -449,6 +454,7 @@ raise socket.error(err, _strerror(err)) self.handle_connect() self.connected = True + self.connecting = False def handle_write_event(self): if self.accepting: @@ -457,12 +463,8 @@ return if not self.connected: - #check for errors - err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) - if err != 0: - raise socket.error(err, _strerror(err)) - - self.handle_connect_event() + if self.connecting: + self.handle_connect_event() self.handle_write() def handle_expt_event(self): diff --git a/lib-python/2.7/bdb.py b/lib-python/2.7/bdb.py --- a/lib-python/2.7/bdb.py +++ b/lib-python/2.7/bdb.py @@ -24,6 +24,7 @@ self.skip = set(skip) if skip else None self.breaks = {} self.fncache = {} + self.frame_returning = None def canonic(self, filename): if filename == "<" + filename[1:-1] + ">": @@ -82,7 +83,11 @@ def dispatch_return(self, frame, arg): if self.stop_here(frame) or frame == self.returnframe: - self.user_return(frame, arg) + try: + self.frame_returning = frame + self.user_return(frame, arg) + finally: + self.frame_returning = None if self.quitting: raise BdbQuit return self.trace_dispatch @@ -186,6 +191,14 @@ def set_step(self): """Stop after one line of code.""" + # Issue #13183: pdb skips frames after hitting a breakpoint and running + # step commands. + # Restore the trace function in the caller (that may not have been set + # for performance reasons) when returning from the current frame. + if self.frame_returning: + caller_frame = self.frame_returning.f_back + if caller_frame and not caller_frame.f_trace: + caller_frame.f_trace = self.trace_dispatch self._set_stopinfo(None, None) def set_next(self, frame): diff --git a/lib-python/2.7/calendar.py b/lib-python/2.7/calendar.py --- a/lib-python/2.7/calendar.py +++ b/lib-python/2.7/calendar.py @@ -161,7 +161,11 @@ oneday = datetime.timedelta(days=1) while True: yield date - date += oneday + try: + date += oneday + except OverflowError: + # Adding one day could fail after datetime.MAXYEAR + break if date.month != month and date.weekday() == self.firstweekday: break @@ -488,6 +492,7 @@ def __enter__(self): self.oldlocale = _locale.getlocale(_locale.LC_TIME) _locale.setlocale(_locale.LC_TIME, self.locale) + return _locale.getlocale(_locale.LC_TIME)[1] def __exit__(self, *args): _locale.setlocale(_locale.LC_TIME, self.oldlocale) diff --git a/lib-python/2.7/cgi.py b/lib-python/2.7/cgi.py --- a/lib-python/2.7/cgi.py +++ b/lib-python/2.7/cgi.py @@ -37,7 +37,6 @@ from operator import attrgetter import sys import os -import urllib import UserDict import urlparse diff --git a/lib-python/2.7/cgitb.py b/lib-python/2.7/cgitb.py --- a/lib-python/2.7/cgitb.py +++ b/lib-python/2.7/cgitb.py @@ -295,14 +295,19 @@ if self.logdir is not None: suffix = ['.txt', '.html'][self.format=="html"] (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir) + try: file = os.fdopen(fd, 'w') file.write(doc) file.close() - msg = '

%s contains the description of this error.' % path + msg = '%s contains the description of this error.' % path except: - msg = '

Tried to save traceback to %s, but failed.' % path - self.file.write(msg + '\n') + msg = 'Tried to save traceback to %s, but failed.' % path + + if self.format == 'html': + self.file.write('

%s

\n' % msg) + else: + self.file.write(msg + '\n') try: self.file.flush() except: pass diff --git a/lib-python/2.7/cmd.py b/lib-python/2.7/cmd.py --- a/lib-python/2.7/cmd.py +++ b/lib-python/2.7/cmd.py @@ -294,6 +294,7 @@ return list(commands | topics) def do_help(self, arg): + 'List available commands with "help" or detailed help with "help cmd".' if arg: # XXX check arg syntax try: diff --git a/lib-python/2.7/collections.py b/lib-python/2.7/collections.py --- a/lib-python/2.7/collections.py +++ b/lib-python/2.7/collections.py @@ -6,11 +6,12 @@ __all__ += _abcoll.__all__ from _collections import deque, defaultdict -from operator import itemgetter as _itemgetter +from operator import itemgetter as _itemgetter, eq as _eq from keyword import iskeyword as _iskeyword import sys as _sys import heapq as _heapq from itertools import repeat as _repeat, chain as _chain, starmap as _starmap +from itertools import imap as _imap try: from thread import get_ident as _get_ident @@ -50,49 +51,45 @@ self.__map = {} self.__update(*args, **kwds) - def __setitem__(self, key, value, PREV=0, NEXT=1, dict_setitem=dict.__setitem__): + def __setitem__(self, key, value, dict_setitem=dict.__setitem__): 'od.__setitem__(i, y) <==> od[i]=y' # Setting a new item creates a new link at the end of the linked list, # and the inherited dictionary is updated with the new key/value pair. if key not in self: root = self.__root - last = root[PREV] - last[NEXT] = root[PREV] = self.__map[key] = [last, root, key] - dict_setitem(self, key, value) + last = root[0] + last[1] = root[0] = self.__map[key] = [last, root, key] + return dict_setitem(self, key, value) - def __delitem__(self, key, PREV=0, NEXT=1, dict_delitem=dict.__delitem__): + def __delitem__(self, key, dict_delitem=dict.__delitem__): 'od.__delitem__(y) <==> del od[y]' # Deleting an existing item uses self.__map to find the link which gets # removed by updating the links in the predecessor and successor nodes. dict_delitem(self, key) link_prev, link_next, key = self.__map.pop(key) - link_prev[NEXT] = link_next - link_next[PREV] = link_prev + link_prev[1] = link_next # update link_prev[NEXT] + link_next[0] = link_prev # update link_next[PREV] def __iter__(self): 'od.__iter__() <==> iter(od)' # Traverse the linked list in order. - NEXT, KEY = 1, 2 root = self.__root - curr = root[NEXT] + curr = root[1] # start at the first node while curr is not root: - yield curr[KEY] - curr = curr[NEXT] + yield curr[2] # yield the curr[KEY] + curr = curr[1] # move to next node def __reversed__(self): 'od.__reversed__() <==> reversed(od)' # Traverse the linked list in reverse order. - PREV, KEY = 0, 2 root = self.__root - curr = root[PREV] + curr = root[0] # start at the last node while curr is not root: - yield curr[KEY] - curr = curr[PREV] + yield curr[2] # yield the curr[KEY] + curr = curr[0] # move to previous node def clear(self): 'od.clear() -> None. Remove all items from od.' - for node in self.__map.itervalues(): - del node[:] root = self.__root root[:] = [root, root, None] self.__map.clear() @@ -208,7 +205,7 @@ ''' if isinstance(other, OrderedDict): - return len(self)==len(other) and self.items() == other.items() + return dict.__eq__(self, other) and all(_imap(_eq, self, other)) return dict.__eq__(self, other) def __ne__(self, other): @@ -234,10 +231,60 @@ ### namedtuple ################################################################################ +_class_template = '''\ +class {typename}(tuple): + '{typename}({arg_list})' + + __slots__ = () + + _fields = {field_names!r} + + def __new__(_cls, {arg_list}): + 'Create new instance of {typename}({arg_list})' + return _tuple.__new__(_cls, ({arg_list})) + + @classmethod + def _make(cls, iterable, new=tuple.__new__, len=len): + 'Make a new {typename} object from a sequence or iterable' + result = new(cls, iterable) + if len(result) != {num_fields:d}: + raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result)) + return result + + def __repr__(self): + 'Return a nicely formatted representation string' + return '{typename}({repr_fmt})' % self + + def _asdict(self): + 'Return a new OrderedDict which maps field names to their values' + return OrderedDict(zip(self._fields, self)) + + __dict__ = property(_asdict) + + def _replace(_self, **kwds): + 'Return a new {typename} object replacing specified fields with new values' + result = _self._make(map(kwds.pop, {field_names!r}, _self)) + if kwds: + raise ValueError('Got unexpected field names: %r' % kwds.keys()) + return result + + def __getnewargs__(self): + 'Return self as a plain tuple. Used by copy and pickle.' + return tuple(self) + +{field_defs} +''' + +_repr_template = '{name}=%r' + +_field_template = '''\ + {name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}') +''' + def namedtuple(typename, field_names, verbose=False, rename=False): """Returns a new subclass of tuple with named fields. - >>> Point = namedtuple('Point', 'x y') + >>> Point = namedtuple('Point', ['x', 'y']) >>> Point.__doc__ # docstring for the new class 'Point(x, y)' >>> p = Point(11, y=22) # instantiate with positional args or keywords @@ -258,83 +305,63 @@ """ - # Parse and validate the field names. Validation serves two purposes, - # generating informative error messages and preventing template injection attacks. + # Validate the field names. At the user's option, either generate an error + # message or automatically replace the field name with a valid name. if isinstance(field_names, basestring): - field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas - field_names = tuple(map(str, field_names)) + field_names = field_names.replace(',', ' ').split() + field_names = map(str, field_names) if rename: - names = list(field_names) seen = set() - for i, name in enumerate(names): - if (not all(c.isalnum() or c=='_' for c in name) or _iskeyword(name) - or not name or name[0].isdigit() or name.startswith('_') + for index, name in enumerate(field_names): + if (not all(c.isalnum() or c=='_' for c in name) + or _iskeyword(name) + or not name + or name[0].isdigit() + or name.startswith('_') or name in seen): - names[i] = '_%d' % i + field_names[index] = '_%d' % index seen.add(name) - field_names = tuple(names) - for name in (typename,) + field_names: + for name in [typename] + field_names: if not all(c.isalnum() or c=='_' for c in name): - raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name) + raise ValueError('Type names and field names can only contain ' + 'alphanumeric characters and underscores: %r' % name) if _iskeyword(name): - raise ValueError('Type names and field names cannot be a keyword: %r' % name) + raise ValueError('Type names and field names cannot be a ' + 'keyword: %r' % name) if name[0].isdigit(): - raise ValueError('Type names and field names cannot start with a number: %r' % name) - seen_names = set() + raise ValueError('Type names and field names cannot start with ' + 'a number: %r' % name) + seen = set() for name in field_names: if name.startswith('_') and not rename: - raise ValueError('Field names cannot start with an underscore: %r' % name) - if name in seen_names: + raise ValueError('Field names cannot start with an underscore: ' + '%r' % name) + if name in seen: raise ValueError('Encountered duplicate field name: %r' % name) - seen_names.add(name) + seen.add(name) - # Create and fill-in the class template - numfields = len(field_names) - argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes - reprtxt = ', '.join('%s=%%r' % name for name in field_names) - template = '''class %(typename)s(tuple): - '%(typename)s(%(argtxt)s)' \n - __slots__ = () \n - _fields = %(field_names)r \n - def __new__(_cls, %(argtxt)s): - 'Create new instance of %(typename)s(%(argtxt)s)' - return _tuple.__new__(_cls, (%(argtxt)s)) \n - @classmethod - def _make(cls, iterable, new=tuple.__new__, len=len): - 'Make a new %(typename)s object from a sequence or iterable' - result = new(cls, iterable) - if len(result) != %(numfields)d: - raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result)) - return result \n - def __repr__(self): - 'Return a nicely formatted representation string' - return '%(typename)s(%(reprtxt)s)' %% self \n - def _asdict(self): - 'Return a new OrderedDict which maps field names to their values' - return OrderedDict(zip(self._fields, self)) \n - __dict__ = property(_asdict) \n - def _replace(_self, **kwds): - 'Return a new %(typename)s object replacing specified fields with new values' - result = _self._make(map(kwds.pop, %(field_names)r, _self)) - if kwds: - raise ValueError('Got unexpected field names: %%r' %% kwds.keys()) - return result \n - def __getnewargs__(self): - 'Return self as a plain tuple. Used by copy and pickle.' - return tuple(self) \n\n''' % locals() - for i, name in enumerate(field_names): - template += " %s = _property(_itemgetter(%d), doc='Alias for field number %d')\n" % (name, i, i) + # Fill-in the class template + class_definition = _class_template.format( + typename = typename, + field_names = tuple(field_names), + num_fields = len(field_names), + arg_list = repr(tuple(field_names)).replace("'", "")[1:-1], + repr_fmt = ', '.join(_repr_template.format(name=name) + for name in field_names), + field_defs = '\n'.join(_field_template.format(index=index, name=name) + for index, name in enumerate(field_names)) + ) if verbose: - print template + print class_definition - # Execute the template string in a temporary namespace and - # support tracing utilities by setting a value for frame.f_globals['__name__'] + # Execute the template string in a temporary namespace and support + # tracing utilities by setting a value for frame.f_globals['__name__'] namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename, OrderedDict=OrderedDict, _property=property, _tuple=tuple) try: - exec template in namespace - except SyntaxError, e: - raise SyntaxError(e.message + ':\n' + template) + exec class_definition in namespace + except SyntaxError as e: + raise SyntaxError(e.message + ':\n' + class_definition) result = namespace[typename] # For pickling to work, the __module__ variable needs to be set to the frame diff --git a/lib-python/2.7/compiler/consts.py b/lib-python/2.7/compiler/consts.py --- a/lib-python/2.7/compiler/consts.py +++ b/lib-python/2.7/compiler/consts.py @@ -5,7 +5,7 @@ SC_LOCAL = 1 SC_GLOBAL_IMPLICIT = 2 -SC_GLOBAL_EXPLICT = 3 +SC_GLOBAL_EXPLICIT = 3 SC_FREE = 4 SC_CELL = 5 SC_UNKNOWN = 6 diff --git a/lib-python/2.7/compiler/pycodegen.py b/lib-python/2.7/compiler/pycodegen.py --- a/lib-python/2.7/compiler/pycodegen.py +++ b/lib-python/2.7/compiler/pycodegen.py @@ -7,7 +7,7 @@ from compiler import ast, parse, walk, syntax from compiler import pyassem, misc, future, symbols -from compiler.consts import SC_LOCAL, SC_GLOBAL_IMPLICIT, SC_GLOBAL_EXPLICT, \ +from compiler.consts import SC_LOCAL, SC_GLOBAL_IMPLICIT, SC_GLOBAL_EXPLICIT, \ SC_FREE, SC_CELL from compiler.consts import (CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS, CO_NESTED, CO_GENERATOR, CO_FUTURE_DIVISION, @@ -283,7 +283,7 @@ self.emit(prefix + '_NAME', name) else: self.emit(prefix + '_FAST', name) - elif scope == SC_GLOBAL_EXPLICT: + elif scope == SC_GLOBAL_EXPLICIT: self.emit(prefix + '_GLOBAL', name) elif scope == SC_GLOBAL_IMPLICIT: if not self.optimized: diff --git a/lib-python/2.7/compiler/symbols.py b/lib-python/2.7/compiler/symbols.py --- a/lib-python/2.7/compiler/symbols.py +++ b/lib-python/2.7/compiler/symbols.py @@ -1,7 +1,7 @@ """Module symbol-table generator""" from compiler import ast -from compiler.consts import SC_LOCAL, SC_GLOBAL_IMPLICIT, SC_GLOBAL_EXPLICT, \ +from compiler.consts import SC_LOCAL, SC_GLOBAL_IMPLICIT, SC_GLOBAL_EXPLICIT, \ SC_FREE, SC_CELL, SC_UNKNOWN from compiler.misc import mangle import types @@ -90,7 +90,7 @@ The scope of a name could be LOCAL, GLOBAL, FREE, or CELL. """ if name in self.globals: - return SC_GLOBAL_EXPLICT + return SC_GLOBAL_EXPLICIT if name in self.cells: return SC_CELL if name in self.defs: diff --git a/lib-python/2.7/ctypes/test/test_bitfields.py b/lib-python/2.7/ctypes/test/test_bitfields.py --- a/lib-python/2.7/ctypes/test/test_bitfields.py +++ b/lib-python/2.7/ctypes/test/test_bitfields.py @@ -240,5 +240,25 @@ _anonymous_ = ["_"] _fields_ = [("_", X)] + @unittest.skipUnless(hasattr(ctypes, "c_uint32"), "c_int32 is required") + def test_uint32(self): + class X(Structure): + _fields_ = [("a", c_uint32, 32)] + x = X() + x.a = 10 + self.assertEqual(x.a, 10) + x.a = 0xFDCBA987 + self.assertEqual(x.a, 0xFDCBA987) + + @unittest.skipUnless(hasattr(ctypes, "c_uint64"), "c_int64 is required") + def test_uint64(self): + class X(Structure): + _fields_ = [("a", c_uint64, 64)] + x = X() + x.a = 10 + self.assertEqual(x.a, 10) + x.a = 0xFEDCBA9876543211 + self.assertEqual(x.a, 0xFEDCBA9876543211) + if __name__ == "__main__": unittest.main() diff --git a/lib-python/2.7/ctypes/test/test_numbers.py b/lib-python/2.7/ctypes/test/test_numbers.py --- a/lib-python/2.7/ctypes/test/test_numbers.py +++ b/lib-python/2.7/ctypes/test/test_numbers.py @@ -216,6 +216,16 @@ # probably be changed: self.assertRaises(TypeError, c_int, c_long(42)) + def test_float_overflow(self): + import sys + big_int = int(sys.float_info.max) * 2 + for t in float_types + [c_longdouble]: + self.assertRaises(OverflowError, t, big_int) + if (hasattr(t, "__ctype_be__")): + self.assertRaises(OverflowError, t.__ctype_be__, big_int) + if (hasattr(t, "__ctype_le__")): + self.assertRaises(OverflowError, t.__ctype_le__, big_int) + ## def test_perf(self): ## check_perf() diff --git a/lib-python/2.7/ctypes/test/test_returnfuncptrs.py b/lib-python/2.7/ctypes/test/test_returnfuncptrs.py --- a/lib-python/2.7/ctypes/test/test_returnfuncptrs.py +++ b/lib-python/2.7/ctypes/test/test_returnfuncptrs.py @@ -1,5 +1,6 @@ import unittest from ctypes import * +import os import _ctypes_test @@ -31,5 +32,34 @@ self.assertRaises(ArgumentError, strchr, "abcdef", 3) self.assertRaises(TypeError, strchr, "abcdef") + def test_from_dll(self): + dll = CDLL(_ctypes_test.__file__) + # _CFuncPtr instances are now callable with a tuple argument + # which denotes a function name and a dll: + strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)(("my_strchr", dll)) + self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") + self.assertEqual(strchr(b"abcdef", b"x"), None) + self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) + self.assertRaises(TypeError, strchr, b"abcdef") + + # Issue 6083: Reference counting bug + def test_from_dll_refcount(self): + class BadSequence(tuple): + def __getitem__(self, key): + if key == 0: + return "my_strchr" + if key == 1: + return CDLL(_ctypes_test.__file__) + raise IndexError + + # _CFuncPtr instances are now callable with a tuple argument + # which denotes a function name and a dll: + strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)( + BadSequence(("my_strchr", CDLL(_ctypes_test.__file__)))) + self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") + self.assertEqual(strchr(b"abcdef", b"x"), None) + self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) + self.assertRaises(TypeError, strchr, b"abcdef") + if __name__ == "__main__": unittest.main() diff --git a/lib-python/2.7/ctypes/test/test_structures.py b/lib-python/2.7/ctypes/test/test_structures.py --- a/lib-python/2.7/ctypes/test/test_structures.py +++ b/lib-python/2.7/ctypes/test/test_structures.py @@ -1,6 +1,7 @@ import unittest from ctypes import * from struct import calcsize +import _testcapi class SubclassesTest(unittest.TestCase): def test_subclass(self): @@ -199,6 +200,14 @@ "_pack_": -1} self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + # Issue 15989 + d = {"_fields_": [("a", c_byte)], + "_pack_": _testcapi.INT_MAX + 1} + self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + d = {"_fields_": [("a", c_byte)], + "_pack_": _testcapi.UINT_MAX + 2} + self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + def test_initializers(self): class Person(Structure): _fields_ = [("name", c_char*6), diff --git a/lib-python/2.7/ctypes/test/test_win32.py b/lib-python/2.7/ctypes/test/test_win32.py --- a/lib-python/2.7/ctypes/test/test_win32.py +++ b/lib-python/2.7/ctypes/test/test_win32.py @@ -3,6 +3,7 @@ from ctypes import * from ctypes.test import is_resource_enabled import unittest, sys +from test import test_support as support import _ctypes_test @@ -60,7 +61,9 @@ def test_COMError(self): from _ctypes import COMError - self.assertEqual(COMError.__doc__, "Raised when a COM method call failed.") + if support.HAVE_DOCSTRINGS: + self.assertEqual(COMError.__doc__, + "Raised when a COM method call failed.") ex = COMError(-1, "text", ("details",)) self.assertEqual(ex.hresult, -1) diff --git a/lib-python/2.7/ctypes/util.py b/lib-python/2.7/ctypes/util.py --- a/lib-python/2.7/ctypes/util.py +++ b/lib-python/2.7/ctypes/util.py @@ -180,6 +180,35 @@ res.sort(cmp= lambda x,y: cmp(_num_version(x), _num_version(y))) return res[-1] + elif sys.platform == "sunos5": + + def _findLib_crle(name, is64): + if not os.path.exists('/usr/bin/crle'): + return None + + if is64: + cmd = 'env LC_ALL=C /usr/bin/crle -64 2>/dev/null' + else: + cmd = 'env LC_ALL=C /usr/bin/crle 2>/dev/null' + + for line in os.popen(cmd).readlines(): + line = line.strip() + if line.startswith('Default Library Path (ELF):'): + paths = line.split()[4] + + if not paths: + return None + + for dir in paths.split(":"): + libfile = os.path.join(dir, "lib%s.so" % name) + if os.path.exists(libfile): + return libfile + + return None + + def find_library(name, is64 = False): + return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name)) + else: def _findSoname_ldconfig(name): diff --git a/lib-python/2.7/curses/__init__.py b/lib-python/2.7/curses/__init__.py --- a/lib-python/2.7/curses/__init__.py +++ b/lib-python/2.7/curses/__init__.py @@ -5,7 +5,7 @@ import curses from curses import textpad - curses.initwin() + curses.initscr() ... """ diff --git a/lib-python/2.7/decimal.py b/lib-python/2.7/decimal.py --- a/lib-python/2.7/decimal.py +++ b/lib-python/2.7/decimal.py @@ -1581,7 +1581,13 @@ def __float__(self): """Float representation.""" - return float(str(self)) + if self._isnan(): + if self.is_snan(): + raise ValueError("Cannot convert signaling NaN to float") + s = "-nan" if self._sign else "nan" + else: + s = str(self) + return float(s) def __int__(self): """Converts self to an int, truncating if necessary.""" diff --git a/lib-python/2.7/distutils/__init__.py b/lib-python/2.7/distutils/__init__.py --- a/lib-python/2.7/distutils/__init__.py +++ b/lib-python/2.7/distutils/__init__.py @@ -15,5 +15,5 @@ # Updated automatically by the Python release process. # #--start constants-- -__version__ = "2.7.3rc2" +__version__ = "2.7.3" #--end constants-- diff --git a/lib-python/2.7/distutils/ccompiler.py b/lib-python/2.7/distutils/ccompiler.py --- a/lib-python/2.7/distutils/ccompiler.py +++ b/lib-python/2.7/distutils/ccompiler.py @@ -17,6 +17,8 @@ from distutils.dep_util import newer_group from distutils.util import split_quoted, execute from distutils import log +# following import is for backward compatibility +from distutils.sysconfig import customize_compiler class CCompiler: """Abstract base class to define the interface that must be implemented diff --git a/lib-python/2.7/distutils/command/check.py b/lib-python/2.7/distutils/command/check.py --- a/lib-python/2.7/distutils/command/check.py +++ b/lib-python/2.7/distutils/command/check.py @@ -26,6 +26,9 @@ def system_message(self, level, message, *children, **kwargs): self.messages.append((level, message, children, kwargs)) + return nodes.system_message(message, level=level, + type=self.levels[level], + *children, **kwargs) HAS_DOCUTILS = True except ImportError: diff --git a/lib-python/2.7/distutils/config.py b/lib-python/2.7/distutils/config.py --- a/lib-python/2.7/distutils/config.py +++ b/lib-python/2.7/distutils/config.py @@ -42,16 +42,11 @@ def _store_pypirc(self, username, password): """Creates a default .pypirc file.""" rc = self._get_rc_file() - f = open(rc, 'w') + f = os.fdopen(os.open(rc, os.O_CREAT | os.O_WRONLY, 0600), 'w') try: f.write(DEFAULT_PYPIRC % (username, password)) finally: f.close() - try: - os.chmod(rc, 0600) - except OSError: - # should do something better here - pass def _read_pypirc(self): """Reads the .pypirc file.""" diff --git a/lib-python/2.7/distutils/dir_util.py b/lib-python/2.7/distutils/dir_util.py --- a/lib-python/2.7/distutils/dir_util.py +++ b/lib-python/2.7/distutils/dir_util.py @@ -144,6 +144,10 @@ src_name = os.path.join(src, n) dst_name = os.path.join(dst, n) + if n.startswith('.nfs'): + # skip NFS rename files + continue + if preserve_symlinks and os.path.islink(src_name): link_dest = os.readlink(src_name) if verbose >= 1: diff --git a/lib-python/2.7/distutils/sysconfig.py b/lib-python/2.7/distutils/sysconfig.py --- a/lib-python/2.7/distutils/sysconfig.py +++ b/lib-python/2.7/distutils/sysconfig.py @@ -37,6 +37,11 @@ project_base = os.path.abspath(os.path.join(project_base, os.path.pardir, os.path.pardir)) +# set for cross builds +if "_PYTHON_PROJECT_BASE" in os.environ: + # this is the build directory, at least for posix + project_base = os.path.normpath(os.environ["_PYTHON_PROJECT_BASE"]) + # python_build: (Boolean) if true, we're either building Python or # building an extension with an un-installed Python, so we use # different (hard-wired) directories. @@ -141,7 +146,7 @@ "I don't know where Python installs its library " "on platform '%s'" % os.name) -_USE_CLANG = None + def customize_compiler(compiler): """Do any platform-specific customization of a CCompiler instance. @@ -150,6 +155,21 @@ varies across Unices and is stored in Python's Makefile. """ if compiler.compiler_type == "unix": + if sys.platform == "darwin": + # Perform first-time customization of compiler-related + # config vars on OS X now that we know we need a compiler. + # This is primarily to support Pythons from binary + # installers. The kind and paths to build tools on + # the user system may vary significantly from the system + # that Python itself was built on. Also the user OS + # version and build tools may not support the same set + # of CPU architectures for universal builds. + global _config_vars + if not _config_vars.get('CUSTOMIZED_OSX_COMPILER', ''): + import _osx_support + _osx_support.customize_compiler(_config_vars) + _config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True' + (cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags) = \ get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS', 'CCSHARED', 'LDSHARED', 'SO', 'AR', @@ -157,36 +177,7 @@ newcc = None if 'CC' in os.environ: - newcc = os.environ['CC'] - elif sys.platform == 'darwin' and cc == 'gcc-4.2': - # Issue #13590: - # Since Apple removed gcc-4.2 in Xcode 4.2, we can no - # longer assume it is available for extension module builds. - # If Python was built with gcc-4.2, check first to see if - # it is available on this system; if not, try to use clang - # instead unless the caller explicitly set CC. - global _USE_CLANG - if _USE_CLANG is None: - from distutils import log - from subprocess import Popen, PIPE - p = Popen("! type gcc-4.2 && type clang && exit 2", - shell=True, stdout=PIPE, stderr=PIPE) - p.wait() - if p.returncode == 2: - _USE_CLANG = True - log.warn("gcc-4.2 not found, using clang instead") - else: - _USE_CLANG = False - if _USE_CLANG: - newcc = 'clang' - if newcc: - # On OS X, if CC is overridden, use that as the default - # command for LDSHARED as well - if (sys.platform == 'darwin' - and 'LDSHARED' not in os.environ - and ldshared.startswith(cc)): - ldshared = newcc + ldshared[len(cc):] - cc = newcc + cc = os.environ['CC'] if 'CXX' in os.environ: cxx = os.environ['CXX'] if 'LDSHARED' in os.environ: @@ -244,7 +235,7 @@ def get_makefile_filename(): """Return full pathname of installed Makefile from the Python build.""" if python_build: - return os.path.join(os.path.dirname(sys.executable), "Makefile") + return os.path.join(project_base, "Makefile") lib_dir = get_python_lib(plat_specific=1, standard_lib=1) return os.path.join(lib_dir, "config", "Makefile") @@ -518,66 +509,11 @@ _config_vars['prefix'] = PREFIX _config_vars['exec_prefix'] = EXEC_PREFIX + # OS X platforms require special customization to handle + # multi-architecture, multi-os-version installers if sys.platform == 'darwin': - kernel_version = os.uname()[2] # Kernel version (8.4.3) - major_version = int(kernel_version.split('.')[0]) - - if major_version < 8: - # On Mac OS X before 10.4, check if -arch and -isysroot - # are in CFLAGS or LDFLAGS and remove them if they are. - # This is needed when building extensions on a 10.3 system - # using a universal build of python. - for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - flags = _config_vars[key] - flags = re.sub('-arch\s+\w+\s', ' ', flags) - flags = re.sub('-isysroot [^ \t]*', ' ', flags) - _config_vars[key] = flags - - else: - - # Allow the user to override the architecture flags using - # an environment variable. - # NOTE: This name was introduced by Apple in OSX 10.5 and - # is used by several scripting languages distributed with - # that OS release. - - if 'ARCHFLAGS' in os.environ: - arch = os.environ['ARCHFLAGS'] - for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - - flags = _config_vars[key] - flags = re.sub('-arch\s+\w+\s', ' ', flags) - flags = flags + ' ' + arch - _config_vars[key] = flags - - # If we're on OSX 10.5 or later and the user tries to - # compiles an extension using an SDK that is not present - # on the current machine it is better to not use an SDK - # than to fail. - # - # The major usecase for this is users using a Python.org - # binary installer on OSX 10.6: that installer uses - # the 10.4u SDK, but that SDK is not installed by default - # when you install Xcode. - # - m = re.search('-isysroot\s+(\S+)', _config_vars['CFLAGS']) - if m is not None: - sdk = m.group(1) - if not os.path.exists(sdk): - for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - - flags = _config_vars[key] - flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags) - _config_vars[key] = flags + import _osx_support + _osx_support.customize_config_vars(_config_vars) if args: vals = [] diff --git a/lib-python/2.7/distutils/tests/test_build_ext.py b/lib-python/2.7/distutils/tests/test_build_ext.py --- a/lib-python/2.7/distutils/tests/test_build_ext.py +++ b/lib-python/2.7/distutils/tests/test_build_ext.py @@ -77,8 +77,9 @@ self.assertEqual(xx.foo(2, 5), 7) self.assertEqual(xx.foo(13,15), 28) self.assertEqual(xx.new().demo(), None) - doc = 'This is a template module just for instruction.' - self.assertEqual(xx.__doc__, doc) + if test_support.HAVE_DOCSTRINGS: + doc = 'This is a template module just for instruction.' + self.assertEqual(xx.__doc__, doc) self.assertTrue(isinstance(xx.Null(), xx.Null)) self.assertTrue(isinstance(xx.Str(), xx.Str)) diff --git a/lib-python/2.7/distutils/tests/test_dir_util.py b/lib-python/2.7/distutils/tests/test_dir_util.py --- a/lib-python/2.7/distutils/tests/test_dir_util.py +++ b/lib-python/2.7/distutils/tests/test_dir_util.py @@ -101,6 +101,24 @@ remove_tree(self.root_target, verbose=0) remove_tree(self.target2, verbose=0) + def test_copy_tree_skips_nfs_temp_files(self): + mkpath(self.target, verbose=0) + + a_file = os.path.join(self.target, 'ok.txt') + nfs_file = os.path.join(self.target, '.nfs123abc') + for f in a_file, nfs_file: + fh = open(f, 'w') + try: + fh.write('some content') + finally: + fh.close() + + copy_tree(self.target, self.target2) + self.assertEqual(os.listdir(self.target2), ['ok.txt']) + + remove_tree(self.root_target, verbose=0) + remove_tree(self.target2, verbose=0) + def test_ensure_relative(self): if os.sep == '/': self.assertEqual(ensure_relative('/home/foo'), 'home/foo') diff --git a/lib-python/2.7/distutils/tests/test_msvc9compiler.py b/lib-python/2.7/distutils/tests/test_msvc9compiler.py --- a/lib-python/2.7/distutils/tests/test_msvc9compiler.py +++ b/lib-python/2.7/distutils/tests/test_msvc9compiler.py @@ -104,7 +104,7 @@ unittest.TestCase): def test_no_compiler(self): - # makes sure query_vcvarsall throws + # makes sure query_vcvarsall raises # a DistutilsPlatformError if the compiler # is not found from distutils.msvc9compiler import query_vcvarsall diff --git a/lib-python/2.7/distutils/tests/test_register.py b/lib-python/2.7/distutils/tests/test_register.py --- a/lib-python/2.7/distutils/tests/test_register.py +++ b/lib-python/2.7/distutils/tests/test_register.py @@ -1,6 +1,5 @@ # -*- encoding: utf8 -*- """Tests for distutils.command.register.""" -import sys import os import unittest import getpass @@ -11,11 +10,14 @@ from distutils.command import register as register_module from distutils.command.register import register -from distutils.core import Distribution from distutils.errors import DistutilsSetupError -from distutils.tests import support -from distutils.tests.test_config import PYPIRC, PyPIRCCommandTestCase +from distutils.tests.test_config import PyPIRCCommandTestCase + +try: + import docutils +except ImportError: + docutils = None PYPIRC_NOPASSWORD = """\ [distutils] @@ -192,6 +194,7 @@ self.assertEqual(headers['Content-length'], '290') self.assertTrue('tarek' in req.data) + @unittest.skipUnless(docutils is not None, 'needs docutils') def test_strict(self): # testing the script option # when on, the register command stops if @@ -204,13 +207,6 @@ cmd.strict = 1 self.assertRaises(DistutilsSetupError, cmd.run) - # we don't test the reSt feature if docutils - # is not installed - try: - import docutils - except ImportError: - return - # metadata are OK but long_description is broken metadata = {'url': 'xxx', 'author': 'xxx', 'author_email': u'??x??x??', @@ -264,6 +260,21 @@ finally: del register_module.raw_input + @unittest.skipUnless(docutils is not None, 'needs docutils') + def test_register_invalid_long_description(self): + description = ':funkie:`str`' # mimic Sphinx-specific markup + metadata = {'url': 'xxx', 'author': 'xxx', + 'author_email': 'xxx', + 'name': 'xxx', 'version': 'xxx', + 'long_description': description} + cmd = self._get_cmd(metadata) + cmd.ensure_finalized() + cmd.strict = True + inputs = RawInputs('2', 'tarek', 'tarek at ziade.org') + register_module.raw_input = inputs + self.addCleanup(delattr, register_module, 'raw_input') + self.assertRaises(DistutilsSetupError, cmd.run) + def test_check_metadata_deprecated(self): # makes sure make_metadata is deprecated cmd = self._get_cmd() diff --git a/lib-python/2.7/distutils/tests/test_sdist.py b/lib-python/2.7/distutils/tests/test_sdist.py --- a/lib-python/2.7/distutils/tests/test_sdist.py +++ b/lib-python/2.7/distutils/tests/test_sdist.py @@ -91,9 +91,8 @@ @unittest.skipUnless(zlib, "requires zlib") def test_prune_file_list(self): - # this test creates a package with some vcs dirs in it - # and launch sdist to make sure they get pruned - # on all systems + # this test creates a project with some VCS dirs and an NFS rename + # file, then launches sdist to check they get pruned on all systems # creating VCS directories with some files in them os.mkdir(join(self.tmp_dir, 'somecode', '.svn')) @@ -107,6 +106,8 @@ self.write_file((self.tmp_dir, 'somecode', '.git', 'ok'), 'xxx') + self.write_file((self.tmp_dir, 'somecode', '.nfs0001'), 'xxx') + # now building a sdist dist, cmd = self.get_cmd() diff --git a/lib-python/2.7/distutils/tests/test_sysconfig.py b/lib-python/2.7/distutils/tests/test_sysconfig.py --- a/lib-python/2.7/distutils/tests/test_sysconfig.py +++ b/lib-python/2.7/distutils/tests/test_sysconfig.py @@ -72,6 +72,35 @@ 'OTHER': 'foo'}) + def test_sysconfig_module(self): + import sysconfig as global_sysconfig + self.assertEqual(global_sysconfig.get_config_var('CFLAGS'), sysconfig.get_config_var('CFLAGS')) + self.assertEqual(global_sysconfig.get_config_var('LDFLAGS'), sysconfig.get_config_var('LDFLAGS')) + + @unittest.skipIf(sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'),'compiler flags customized') + def test_sysconfig_compiler_vars(self): + # On OS X, binary installers support extension module building on + # various levels of the operating system with differing Xcode + # configurations. This requires customization of some of the + # compiler configuration directives to suit the environment on + # the installed machine. Some of these customizations may require + # running external programs and, so, are deferred until needed by + # the first extension module build. With Python 3.3, only + # the Distutils version of sysconfig is used for extension module + # builds, which happens earlier in the Distutils tests. This may + # cause the following tests to fail since no tests have caused + # the global version of sysconfig to call the customization yet. + # The solution for now is to simply skip this test in this case. + # The longer-term solution is to only have one version of sysconfig. + + import sysconfig as global_sysconfig + if sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'): + return + self.assertEqual(global_sysconfig.get_config_var('LDSHARED'), sysconfig.get_config_var('LDSHARED')) + self.assertEqual(global_sysconfig.get_config_var('CC'), sysconfig.get_config_var('CC')) + + + def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(SysconfigTestCase)) diff --git a/lib-python/2.7/distutils/unixccompiler.py b/lib-python/2.7/distutils/unixccompiler.py --- a/lib-python/2.7/distutils/unixccompiler.py +++ b/lib-python/2.7/distutils/unixccompiler.py @@ -26,6 +26,9 @@ DistutilsExecError, CompileError, LibError, LinkError from distutils import log +if sys.platform == 'darwin': + import _osx_support + # XXX Things not currently handled: # * optimization/debug/warning flags; we just use whatever's in Python's # Makefile and live with it. Is this adequate? If not, we might @@ -41,68 +44,6 @@ # should just happily stuff them into the preprocessor/compiler/linker # options and carry on. -def _darwin_compiler_fixup(compiler_so, cc_args): - """ - This function will strip '-isysroot PATH' and '-arch ARCH' from the - compile flags if the user has specified one them in extra_compile_flags. - - This is needed because '-arch ARCH' adds another architecture to the - build, without a way to remove an architecture. Furthermore GCC will - barf if multiple '-isysroot' arguments are present. - """ - stripArch = stripSysroot = 0 - - compiler_so = list(compiler_so) - kernel_version = os.uname()[2] # 8.4.3 - major_version = int(kernel_version.split('.')[0]) - - if major_version < 8: - # OSX before 10.4.0, these don't support -arch and -isysroot at - # all. - stripArch = stripSysroot = True - else: - stripArch = '-arch' in cc_args - stripSysroot = '-isysroot' in cc_args - - if stripArch or 'ARCHFLAGS' in os.environ: - while 1: - try: - index = compiler_so.index('-arch') - # Strip this argument and the next one: - del compiler_so[index:index+2] - except ValueError: - break - - if 'ARCHFLAGS' in os.environ and not stripArch: - # User specified different -arch flags in the environ, - # see also distutils.sysconfig - compiler_so = compiler_so + os.environ['ARCHFLAGS'].split() - - if stripSysroot: - try: - index = compiler_so.index('-isysroot') - # Strip this argument and the next one: - del compiler_so[index:index+2] - except ValueError: - pass - - # Check if the SDK that is used during compilation actually exists, - # the universal build requires the usage of a universal SDK and not all - # users have that installed by default. - sysroot = None - if '-isysroot' in cc_args: - idx = cc_args.index('-isysroot') - sysroot = cc_args[idx+1] - elif '-isysroot' in compiler_so: - idx = compiler_so.index('-isysroot') - sysroot = compiler_so[idx+1] - - if sysroot and not os.path.isdir(sysroot): - log.warn("Compiling with an SDK that doesn't seem to exist: %s", - sysroot) - log.warn("Please check your Xcode installation") - - return compiler_so class UnixCCompiler(CCompiler): @@ -172,7 +113,8 @@ def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): compiler_so = self.compiler_so if sys.platform == 'darwin': - compiler_so = _darwin_compiler_fixup(compiler_so, cc_args + extra_postargs) + compiler_so = _osx_support.compiler_fixup(compiler_so, + cc_args + extra_postargs) try: self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs) @@ -251,7 +193,7 @@ linker[i] = self.compiler_cxx[i] if sys.platform == 'darwin': - linker = _darwin_compiler_fixup(linker, ld_args) + linker = _osx_support.compiler_fixup(linker, ld_args) self.spawn(linker + ld_args) except DistutilsExecError, msg: diff --git a/lib-python/2.7/distutils/util.py b/lib-python/2.7/distutils/util.py --- a/lib-python/2.7/distutils/util.py +++ b/lib-python/2.7/distutils/util.py @@ -51,6 +51,10 @@ return 'win-ia64' return sys.platform + # Set for cross builds explicitly + if "_PYTHON_HOST_PLATFORM" in os.environ: + return os.environ["_PYTHON_HOST_PLATFORM"] + if os.name != "posix" or not hasattr(os, 'uname'): # XXX what about the architecture? NT is Intel or Alpha, # Mac OS is M68k or PPC, etc. @@ -93,94 +97,10 @@ if m: release = m.group() elif osname[:6] == "darwin": - # - # For our purposes, we'll assume that the system version from - # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set - # to. This makes the compatibility story a bit more sane because the - # machine is going to compile and link as if it were - # MACOSX_DEPLOYMENT_TARGET. - from distutils.sysconfig import get_config_vars - cfgvars = get_config_vars() - - macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET') - - if 1: - # Always calculate the release of the running machine, - # needed to determine if we can build fat binaries or not. - - macrelease = macver - # Get the system version. Reading this plist is a documented - # way to get the system version (see the documentation for - # the Gestalt Manager) - try: - f = open('/System/Library/CoreServices/SystemVersion.plist') - except IOError: - # We're on a plain darwin box, fall back to the default - # behaviour. - pass - else: - try: - m = re.search( - r'ProductUserVisibleVersion\s*' + - r'(.*?)', f.read()) - if m is not None: - macrelease = '.'.join(m.group(1).split('.')[:2]) - # else: fall back to the default behaviour - finally: - f.close() - - if not macver: - macver = macrelease - - if macver: - from distutils.sysconfig import get_config_vars - release = macver - osname = "macosx" - - if (macrelease + '.') >= '10.4.' and \ - '-arch' in get_config_vars().get('CFLAGS', '').strip(): - # The universal build will build fat binaries, but not on - # systems before 10.4 - # - # Try to detect 4-way universal builds, those have machine-type - # 'universal' instead of 'fat'. - - machine = 'fat' - cflags = get_config_vars().get('CFLAGS') - - archs = re.findall('-arch\s+(\S+)', cflags) - archs = tuple(sorted(set(archs))) - - if len(archs) == 1: - machine = archs[0] - elif archs == ('i386', 'ppc'): - machine = 'fat' - elif archs == ('i386', 'x86_64'): - machine = 'intel' - elif archs == ('i386', 'ppc', 'x86_64'): - machine = 'fat3' - elif archs == ('ppc64', 'x86_64'): - machine = 'fat64' - elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'): - machine = 'universal' - else: - raise ValueError( - "Don't know machine value for archs=%r"%(archs,)) - - elif machine == 'i386': - # On OSX the machine type returned by uname is always the - # 32-bit variant, even if the executable architecture is - # the 64-bit variant - if sys.maxint >= 2**32: - machine = 'x86_64' - - elif machine in ('PowerPC', 'Power_Macintosh'): - # Pick a sane name for the PPC architecture. - machine = 'ppc' - - # See 'i386' case - if sys.maxint >= 2**32: - machine = 'ppc64' + import _osx_support, distutils.sysconfig + osname, release, machine = _osx_support.get_platform_osx( + distutils.sysconfig.get_config_vars(), + osname, release, machine) return "%s-%s-%s" % (osname, release, machine) diff --git a/lib-python/2.7/doctest.py b/lib-python/2.7/doctest.py --- a/lib-python/2.7/doctest.py +++ b/lib-python/2.7/doctest.py @@ -2314,7 +2314,8 @@ return "Doctest: " + self._dt_test.name class SkipDocTestCase(DocTestCase): - def __init__(self): + def __init__(self, module): + self.module = module DocTestCase.__init__(self, None) def setUp(self): @@ -2324,7 +2325,10 @@ pass def shortDescription(self): - return "Skipping tests from %s" % module.__name__ + return "Skipping tests from %s" % self.module.__name__ + + __str__ = shortDescription + def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, **options): @@ -2372,12 +2376,17 @@ if not tests and sys.flags.optimize >=2: # Skip doctests when running with -O2 suite = unittest.TestSuite() - suite.addTest(SkipDocTestCase()) + suite.addTest(SkipDocTestCase(module)) return suite elif not tests: # Why do we want to do this? Because it reveals a bug that might # otherwise be hidden. - raise ValueError(module, "has no tests") + # It is probably a bug that this exception is not also raised if the + # number of doctest examples in tests is zero (i.e. if no doctest + # examples were found). However, we should probably not be raising + # an exception at all here, though it is too late to make this change + # for a maintenance release. See also issue #14649. + raise ValueError(module, "has no docstrings") tests.sort() suite = unittest.TestSuite() diff --git a/lib-python/2.7/email/_parseaddr.py b/lib-python/2.7/email/_parseaddr.py --- a/lib-python/2.7/email/_parseaddr.py +++ b/lib-python/2.7/email/_parseaddr.py @@ -13,7 +13,7 @@ 'quote', ] -import time +import time, calendar SPACE = ' ' EMPTYSTRING = '' @@ -150,13 +150,13 @@ def mktime_tz(data): - """Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp.""" + """Turn a 10-tuple as returned by parsedate_tz() into a POSIX timestamp.""" if data[9] is None: # No zone info, so localtime is better assumption than GMT return time.mktime(data[:8] + (-1,)) else: - t = time.mktime(data[:8] + (0,)) - return t - data[9] - time.timezone + t = calendar.timegm(data) + return t - data[9] def quote(str): diff --git a/lib-python/2.7/email/base64mime.py b/lib-python/2.7/email/base64mime.py --- a/lib-python/2.7/email/base64mime.py +++ b/lib-python/2.7/email/base64mime.py @@ -130,7 +130,7 @@ verbatim (this is the default). Each line of encoded text will end with eol, which defaults to "\\n". Set - this to "\r\n" if you will be using the result of this function directly + this to "\\r\\n" if you will be using the result of this function directly in an email. """ if not s: diff --git a/lib-python/2.7/email/feedparser.py b/lib-python/2.7/email/feedparser.py --- a/lib-python/2.7/email/feedparser.py +++ b/lib-python/2.7/email/feedparser.py @@ -13,7 +13,7 @@ data. When you have no more data to push into the parser, call .close(). This completes the parsing and returns the root message object. -The other advantage of this parser is that it will never throw a parsing +The other advantage of this parser is that it will never raise a parsing exception. Instead, when it finds something unexpected, it adds a 'defect' to the current message. Defects are just instances that live on the message object's .defects attribute. @@ -214,7 +214,7 @@ # supposed to see in the body of the message. self._parse_headers(headers) # Headers-only parsing is a backwards compatibility hack, which was - # necessary in the older parser, which could throw errors. All + # necessary in the older parser, which could raise errors. All # remaining lines in the input are thrown into the message body. if self._headersonly: lines = [] diff --git a/lib-python/2.7/email/generator.py b/lib-python/2.7/email/generator.py --- a/lib-python/2.7/email/generator.py +++ b/lib-python/2.7/email/generator.py @@ -212,7 +212,11 @@ msg.set_boundary(boundary) # If there's a preamble, write it out, with a trailing CRLF if msg.preamble is not None: - print >> self._fp, msg.preamble + if self._mangle_from_: + preamble = fcre.sub('>From ', msg.preamble) + else: + preamble = msg.preamble + print >> self._fp, preamble # dash-boundary transport-padding CRLF print >> self._fp, '--' + boundary # body-part @@ -230,7 +234,11 @@ self._fp.write('\n--' + boundary + '--') if msg.epilogue is not None: print >> self._fp - self._fp.write(msg.epilogue) + if self._mangle_from_: + epilogue = fcre.sub('>From ', msg.epilogue) + else: + epilogue = msg.epilogue + self._fp.write(epilogue) def _handle_multipart_signed(self, msg): # The contents of signed parts has to stay unmodified in order to keep diff --git a/lib-python/2.7/email/test/test_email.py b/lib-python/2.7/email/test/test_email.py --- a/lib-python/2.7/email/test/test_email.py +++ b/lib-python/2.7/email/test/test_email.py @@ -9,6 +9,7 @@ import difflib import unittest import warnings +import textwrap from cStringIO import StringIO import email @@ -948,6 +949,28 @@ Blah blah blah """) + def test_mangle_from_in_preamble_and_epilog(self): + s = StringIO() + g = Generator(s, mangle_from_=True) + msg = email.message_from_string(textwrap.dedent("""\ + From: foo at bar.com + Mime-Version: 1.0 + Content-Type: multipart/mixed; boundary=XXX + + From somewhere unknown + + --XXX + Content-Type: text/plain + + foo + + --XXX-- + + From somewhere unknowable + """)) + g.flatten(msg) + self.assertEqual(len([1 for x in s.getvalue().split('\n') + if x.startswith('>From ')]), 2) # Test the basic MIMEAudio class @@ -2262,6 +2285,12 @@ eq(time.localtime(t)[:6], timetup[:6]) eq(int(time.strftime('%Y', timetup[:9])), 2003) + def test_mktime_tz(self): + self.assertEqual(Utils.mktime_tz((1970, 1, 1, 0, 0, 0, + -1, -1, -1, 0)), 0) + self.assertEqual(Utils.mktime_tz((1970, 1, 1, 0, 0, 0, + -1, -1, -1, 1234)), -1234) + def test_parsedate_y2k(self): """Test for parsing a date with a two-digit year. diff --git a/lib-python/2.7/email/test/test_email_renamed.py b/lib-python/2.7/email/test/test_email_renamed.py --- a/lib-python/2.7/email/test/test_email_renamed.py +++ b/lib-python/2.7/email/test/test_email_renamed.py @@ -994,6 +994,38 @@ eq(msg.get_payload(), '+vv8/f7/') eq(msg.get_payload(decode=True), bytes) + def test_binary_body_with_encode_7or8bit(self): + # Issue 17171. + bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff' + msg = MIMEApplication(bytesdata, _encoder=encoders.encode_7or8bit) + # Treated as a string, this will be invalid code points. + self.assertEqual(msg.get_payload(), bytesdata) + self.assertEqual(msg.get_payload(decode=True), bytesdata) + self.assertEqual(msg['Content-Transfer-Encoding'], '8bit') + s = StringIO() + g = Generator(s) + g.flatten(msg) + wireform = s.getvalue() + msg2 = email.message_from_string(wireform) + self.assertEqual(msg.get_payload(), bytesdata) + self.assertEqual(msg2.get_payload(decode=True), bytesdata) + self.assertEqual(msg2['Content-Transfer-Encoding'], '8bit') + + def test_binary_body_with_encode_noop(self): + # Issue 16564: This does not produce an RFC valid message, since to be + # valid it should have a CTE of binary. But the below works, and is + # documented as working this way. + bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff' + msg = MIMEApplication(bytesdata, _encoder=encoders.encode_noop) + self.assertEqual(msg.get_payload(), bytesdata) + self.assertEqual(msg.get_payload(decode=True), bytesdata) + s = StringIO() + g = Generator(s) + g.flatten(msg) + wireform = s.getvalue() + msg2 = email.message_from_string(wireform) + self.assertEqual(msg.get_payload(), bytesdata) + self.assertEqual(msg2.get_payload(decode=True), bytesdata) # Test the basic MIMEText class diff --git a/lib-python/2.7/email/utils.py b/lib-python/2.7/email/utils.py --- a/lib-python/2.7/email/utils.py +++ b/lib-python/2.7/email/utils.py @@ -63,7 +63,7 @@ """Decodes a base64 string. This function is equivalent to base64.decodestring and it's retained only - for backward compatibility. It used to remove the last \n of the decoded + for backward compatibility. It used to remove the last \\n of the decoded string, if it had any (see issue 7143). """ if not s: @@ -73,7 +73,7 @@ def fix_eols(s): - """Replace all line-ending characters with \r\n.""" + """Replace all line-ending characters with \\r\\n.""" # Fix newlines with no preceding carriage return s = re.sub(r'(? self.extrasize: - self._read(readsize) - readsize = min(self.max_read_chunk, readsize * 2) - except EOFError: - if size > self.extrasize: - size = self.extrasize + while size > self.extrasize: + if not self._read(readsize): + if size > self.extrasize: + size = self.extrasize + break + readsize = min(self.max_read_chunk, readsize * 2) offset = self.offset - self.extrastart chunk = self.extrabuf[offset: offset + size] @@ -272,7 +274,7 @@ def _read(self, size=1024): if self.fileobj is None: - raise EOFError, "Reached EOF" + return False if self._new_member: # If the _new_member flag is set, we have to @@ -283,7 +285,7 @@ pos = self.fileobj.tell() # Save current position self.fileobj.seek(0, 2) # Seek to end of file if pos == self.fileobj.tell(): - raise EOFError, "Reached EOF" + return False else: self.fileobj.seek( pos ) # Return to original position @@ -300,9 +302,10 @@ if buf == "": uncompress = self.decompress.flush() + self.fileobj.seek(-len(self.decompress.unused_data), 1) self._read_eof() self._add_read_data( uncompress ) - raise EOFError, 'Reached EOF' + return False uncompress = self.decompress.decompress(buf) self._add_read_data( uncompress ) @@ -312,13 +315,14 @@ # so seek back to the start of the unused data, finish up # this member, and read a new gzip header. # (The number of bytes to seek back is the length of the unused - # data, minus 8 because _read_eof() will rewind a further 8 bytes) - self.fileobj.seek( -len(self.decompress.unused_data)+8, 1) + # data) + self.fileobj.seek(-len(self.decompress.unused_data), 1) # Check the CRC and file size, and set the flag so we read # a new member on the next call self._read_eof() self._new_member = True + return True def _add_read_data(self, data): self.crc = zlib.crc32(data, self.crc) & 0xffffffffL @@ -329,14 +333,11 @@ self.size = self.size + len(data) def _read_eof(self): - # We've read to the end of the file, so we have to rewind in order - # to reread the 8 bytes containing the CRC and the file size. + # We've read to the end of the file. # We check the that the computed CRC and size of the # uncompressed data matches the stored values. Note that the size # stored is the true file size mod 2**32. - self.fileobj.seek(-8, 1) - crc32 = read32(self.fileobj) - isize = read32(self.fileobj) # may exceed 2GB + crc32, isize = struct.unpack(" Largest of the nsmallest - for elem in it: - if cmp_lt(elem, los): - insort(result, elem) - pop() - los = result[-1] + it = iter(iterable) + result = list(islice(it, n)) + if not result: return result - # An alternative approach manifests the whole iterable in memory but - # saves comparisons by heapifying all at once. Also, saves time - # over bisect.insort() which has O(n) data movement time for every - # insertion. Finding the n smallest of an m length iterable requires - # O(m) + O(n log m) comparisons. - h = list(iterable) - heapify(h) - return map(heappop, repeat(h, min(n, len(h)))) + _heapify_max(result) + _heappushpop = _heappushpop_max + for elem in it: + _heappushpop(result, elem) + result.sort() + return result # 'heap' is a heap at all indices >= startpos, except possibly for pos. pos # is the index of a leaf with a possibly out-of-order value. Restore the @@ -314,6 +312,42 @@ heap[pos] = newitem _siftdown(heap, startpos, pos) +def _siftdown_max(heap, startpos, pos): + 'Maxheap variant of _siftdown' + newitem = heap[pos] + # Follow the path to the root, moving parents down until finding a place + # newitem fits. + while pos > startpos: + parentpos = (pos - 1) >> 1 + parent = heap[parentpos] + if cmp_lt(parent, newitem): + heap[pos] = parent + pos = parentpos + continue + break + heap[pos] = newitem + +def _siftup_max(heap, pos): + 'Maxheap variant of _siftup' + endpos = len(heap) + startpos = pos + newitem = heap[pos] + # Bubble up the larger child until hitting a leaf. + childpos = 2*pos + 1 # leftmost child position + while childpos < endpos: + # Set childpos to index of larger child. + rightpos = childpos + 1 + if rightpos < endpos and not cmp_lt(heap[rightpos], heap[childpos]): + childpos = rightpos + # Move the larger child up. + heap[pos] = heap[childpos] + pos = childpos + childpos = 2*pos + 1 + # The leaf at pos is empty now. Put newitem there, and bubble it up + # to its final resting place (by sifting its parents down). + heap[pos] = newitem + _siftdown_max(heap, startpos, pos) + # If available, use C implementation try: from _heapq import * diff --git a/lib-python/2.7/httplib.py b/lib-python/2.7/httplib.py --- a/lib-python/2.7/httplib.py +++ b/lib-python/2.7/httplib.py @@ -362,7 +362,9 @@ def _read_status(self): # Initialize with Simple-Response defaults - line = self.fp.readline() + line = self.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise LineTooLong("header line") if self.debuglevel > 0: print "reply:", repr(line) if not line: @@ -545,7 +547,11 @@ if self.length is None: s = self.fp.read() else: - s = self._safe_read(self.length) + try: + s = self._safe_read(self.length) + except IncompleteRead: + self.close() + raise self.length = 0 self.close() # we read everything return s @@ -559,10 +565,15 @@ # connection, and the user is reading more bytes than will be provided # (for example, reading in 1k chunks) s = self.fp.read(amt) + if not s: + # Ideally, we would raise IncompleteRead if the content-length + # wasn't satisfied, but it might break compatibility. + self.close() if self.length is not None: self.length -= len(s) if not self.length: self.close() + return s def _read_chunked(self, amt): @@ -748,7 +759,11 @@ line = response.fp.readline(_MAXLINE + 1) if len(line) > _MAXLINE: raise LineTooLong("header line") - if line == '\r\n': break + if not line: + # for sites which EOF without sending trailer + break + if line == '\r\n': + break def connect(self): @@ -985,7 +1000,7 @@ self.putrequest(method, url, **skips) - if body and ('content-length' not in header_names): + if body is not None and 'content-length' not in header_names: self._set_content_length(body) for hdr, value in headers.iteritems(): self.putheader(hdr, value) @@ -1058,7 +1073,7 @@ if port == 0: port = None - # Note that we may pass an empty string as the host; this will throw + # Note that we may pass an empty string as the host; this will raise # an error when we attempt to connect. Presumably, the client code # will call connect before then, with a proper host. self._setup(self._connection_class(host, port, strict)) diff --git a/lib-python/2.7/idlelib/CallTips.py b/lib-python/2.7/idlelib/CallTips.py --- a/lib-python/2.7/idlelib/CallTips.py +++ b/lib-python/2.7/idlelib/CallTips.py @@ -71,16 +71,16 @@ if not sur_paren: return hp.set_index(sur_paren[0]) - name = hp.get_expression() - if not name or (not evalfuncs and name.find('(') != -1): + expression = hp.get_expression() + if not expression or (not evalfuncs and expression.find('(') != -1): return - arg_text = self.fetch_tip(name) + arg_text = self.fetch_tip(expression) if not arg_text: return self.calltip = self._make_calltip_window() self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1]) - def fetch_tip(self, name): + def fetch_tip(self, expression): """Return the argument list and docstring of a function or class If there is a Python subprocess, get the calltip there. Otherwise, @@ -96,23 +96,27 @@ """ try: rpcclt = self.editwin.flist.pyshell.interp.rpcclt - except: + except AttributeError: rpcclt = None if rpcclt: return rpcclt.remotecall("exec", "get_the_calltip", - (name,), {}) + (expression,), {}) else: - entity = self.get_entity(name) + entity = self.get_entity(expression) return get_arg_text(entity) - def get_entity(self, name): - "Lookup name in a namespace spanning sys.modules and __main.dict__" - if name: + def get_entity(self, expression): + """Return the object corresponding to expression evaluated + in a namespace spanning sys.modules and __main.dict__. + """ + if expression: namespace = sys.modules.copy() namespace.update(__main__.__dict__) try: - return eval(name, namespace) - except (NameError, AttributeError): + return eval(expression, namespace) + except BaseException: + # An uncaught exception closes idle, and eval can raise any + # exception, especially if user classes are involved. return None def _find_constructor(class_ob): @@ -127,9 +131,10 @@ return None def get_arg_text(ob): - """Get a string describing the arguments for the given object""" + """Get a string describing the arguments for the given object, + only if it is callable.""" arg_text = "" - if ob is not None: + if ob is not None and hasattr(ob, '__call__'): arg_offset = 0 if type(ob) in (types.ClassType, types.TypeType): # Look for the highest __init__ in the class chain. diff --git a/lib-python/2.7/idlelib/ColorDelegator.py b/lib-python/2.7/idlelib/ColorDelegator.py --- a/lib-python/2.7/idlelib/ColorDelegator.py +++ b/lib-python/2.7/idlelib/ColorDelegator.py @@ -20,10 +20,11 @@ # 1st 'file' colorized normal, 2nd as builtin, 3rd as string builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b" comment = any("COMMENT", [r"#[^\n]*"]) - sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?" - dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?' - sq3string = r"(\b[rRuU])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?" - dq3string = r'(\b[rRuU])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?' + stringprefix = r"(\br|u|ur|R|U|UR|Ur|uR|b|B|br|Br|bR|BR)?" + sqstring = stringprefix + r"'[^'\\\n]*(\\.[^'\\\n]*)*'?" + dqstring = stringprefix + r'"[^"\\\n]*(\\.[^"\\\n]*)*"?' + sq3string = stringprefix + r"'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?" + dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?' string = any("STRING", [sq3string, dq3string, sqstring, dqstring]) return kw + "|" + builtin + "|" + comment + "|" + string +\ "|" + any("SYNC", [r"\n"]) diff --git a/lib-python/2.7/idlelib/EditorWindow.py b/lib-python/2.7/idlelib/EditorWindow.py --- a/lib-python/2.7/idlelib/EditorWindow.py +++ b/lib-python/2.7/idlelib/EditorWindow.py @@ -172,13 +172,13 @@ 'recent-files.lst') self.text_frame = text_frame = Frame(top) self.vbar = vbar = Scrollbar(text_frame, name='vbar') - self.width = idleConf.GetOption('main','EditorWindow','width') + self.width = idleConf.GetOption('main','EditorWindow','width', type='int') text_options = { 'name': 'text', 'padx': 5, 'wrap': 'none', 'width': self.width, - 'height': idleConf.GetOption('main', 'EditorWindow', 'height')} + 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')} if TkVersion >= 8.5: # Starting with tk 8.5 we have to set the new tabstyle option # to 'wordprocessor' to achieve the same display of tabs as in @@ -255,7 +255,8 @@ if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'): fontWeight='bold' text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'), - idleConf.GetOption('main', 'EditorWindow', 'font-size'), + idleConf.GetOption('main', 'EditorWindow', + 'font-size', type='int'), fontWeight)) text_frame.pack(side=LEFT, fill=BOTH, expand=1) text.pack(side=TOP, fill=BOTH, expand=1) @@ -470,7 +471,6 @@ rmenu = None def right_menu_event(self, event): - self.text.tag_remove("sel", "1.0", "end") self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) if not self.rmenu: self.make_rmenu() @@ -479,23 +479,52 @@ iswin = sys.platform[:3] == 'win' if iswin: self.text.config(cursor="arrow") + + for label, eventname, verify_state in self.rmenu_specs: + if verify_state is None: + continue + state = getattr(self, verify_state)() + rmenu.entryconfigure(label, state=state) + rmenu.tk_popup(event.x_root, event.y_root) if iswin: self.text.config(cursor="ibeam") rmenu_specs = [ - # ("Label", "<>"), ... - ("Close", "<>"), # Example + # ("Label", "<>", "statefuncname"), ... + ("Close", "<>", None), # Example ] def make_rmenu(self): rmenu = Menu(self.text, tearoff=0) - for label, eventname in self.rmenu_specs: - def command(text=self.text, eventname=eventname): - text.event_generate(eventname) - rmenu.add_command(label=label, command=command) + for label, eventname, _ in self.rmenu_specs: + if label is not None: + def command(text=self.text, eventname=eventname): + text.event_generate(eventname) + rmenu.add_command(label=label, command=command) + else: + rmenu.add_separator() self.rmenu = rmenu + def rmenu_check_cut(self): + return self.rmenu_check_copy() + + def rmenu_check_copy(self): + try: + indx = self.text.index('sel.first') + except TclError: + return 'disabled' + else: + return 'normal' if indx else 'disabled' + + def rmenu_check_paste(self): + try: + self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD') + except TclError: + return 'disabled' + else: + return 'normal' + def about_dialog(self, event=None): aboutDialog.AboutDialog(self.top,'About IDLE') @@ -735,7 +764,8 @@ if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'): fontWeight='bold' self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'), - idleConf.GetOption('main','EditorWindow','font-size'), + idleConf.GetOption('main','EditorWindow','font-size', + type='int'), fontWeight)) def RemoveKeybindings(self): @@ -856,7 +886,7 @@ # for each edit window instance, construct the recent files menu for instance in self.top.instance_dict.keys(): menu = instance.recent_files_menu - menu.delete(1, END) # clear, and rebuild: + menu.delete(0, END) # clear, and rebuild: for i, file_name in enumerate(rf_list): file_name = file_name.rstrip() # zap \n # make unicode string to display non-ASCII chars correctly @@ -1581,7 +1611,7 @@ try: try: _tokenize.tokenize(self.readline, self.tokeneater) - except _tokenize.TokenError: + except (_tokenize.TokenError, SyntaxError): # since we cut off the tokenizer early, we can trigger # spurious errors pass diff --git a/lib-python/2.7/idlelib/FormatParagraph.py b/lib-python/2.7/idlelib/FormatParagraph.py --- a/lib-python/2.7/idlelib/FormatParagraph.py +++ b/lib-python/2.7/idlelib/FormatParagraph.py @@ -32,7 +32,8 @@ self.editwin = None def format_paragraph_event(self, event): - maxformatwidth = int(idleConf.GetOption('main','FormatParagraph','paragraph')) + maxformatwidth = int(idleConf.GetOption('main','FormatParagraph', + 'paragraph', type='int')) text = self.editwin.text first, last = self.editwin.get_selection_indices() if first and last: diff --git a/lib-python/2.7/idlelib/HyperParser.py b/lib-python/2.7/idlelib/HyperParser.py --- a/lib-python/2.7/idlelib/HyperParser.py +++ b/lib-python/2.7/idlelib/HyperParser.py @@ -232,6 +232,11 @@ pass else: # We can't continue after other types of brackets + if rawtext[pos] in "'\"": + # Scan a string prefix + while pos > 0 and rawtext[pos - 1] in "rRbBuU": + pos -= 1 + last_identifier_pos = pos break else: diff --git a/lib-python/2.7/idlelib/IOBinding.py b/lib-python/2.7/idlelib/IOBinding.py --- a/lib-python/2.7/idlelib/IOBinding.py +++ b/lib-python/2.7/idlelib/IOBinding.py @@ -7,6 +7,7 @@ import os import types +import pipes import sys import codecs import tempfile @@ -196,29 +197,33 @@ self.filename_change_hook() def open(self, event=None, editFile=None): - if self.editwin.flist: + flist = self.editwin.flist + # Save in case parent window is closed (ie, during askopenfile()). + if flist: if not editFile: filename = self.askopenfile() else: filename=editFile if filename: - # If the current window has no filename and hasn't been - # modified, we replace its contents (no loss). Otherwise - # we open a new window. But we won't replace the - # shell window (which has an interp(reter) attribute), which - # gets set to "not modified" at every new prompt. - try: - interp = self.editwin.interp - except AttributeError: - interp = None - if not self.filename and self.get_saved() and not interp: - self.editwin.flist.open(filename, self.loadfile) + # If editFile is valid and already open, flist.open will + # shift focus to its existing window. + # If the current window exists and is a fresh unnamed, + # unmodified editor window (not an interpreter shell), + # pass self.loadfile to flist.open so it will load the file + # in the current window (if the file is not already open) + # instead of a new window. + if (self.editwin and + not getattr(self.editwin, 'interp', None) and + not self.filename and + self.get_saved()): + flist.open(filename, self.loadfile) else: - self.editwin.flist.open(filename) + flist.open(filename) else: - self.text.focus_set() + if self.text: + self.text.focus_set() return "break" - # + # Code for use outside IDLE: if self.get_saved(): reply = self.maybesave() @@ -499,7 +504,7 @@ else: #no printing for this platform printPlatform = False if printPlatform: #we can try to print for this platform - command = command % filename + command = command % pipes.quote(filename) pipe = os.popen(command, "r") # things can get ugly on NT if there is no printer available. output = pipe.read().strip() diff --git a/lib-python/2.7/idlelib/NEWS.txt b/lib-python/2.7/idlelib/NEWS.txt --- a/lib-python/2.7/idlelib/NEWS.txt +++ b/lib-python/2.7/idlelib/NEWS.txt @@ -1,5 +1,38 @@ +What's New in IDLE 2.7.4? +========================= + +- Issue #15318: Prevent writing to sys.stdin. + +- Issue #13532, #15319: Check that arguments to sys.stdout.write are strings. + +- Issue # 12510: Attempt to get certain tool tips no longer crashes IDLE. + +- Issue10365: File open dialog now works instead of crashing even when + parent window is closed while dialog is open. + +- Issue 14876: use user-selected font for highlight configuration. + +- Issue #14018: Update checks for unstable system Tcl/Tk versions on OS X + to include versions shipped with OS X 10.7 and 10.8 in addition to 10.6. + +- Issue #15853: Prevent IDLE crash on OS X when opening Preferences menu + with certain versions of Tk 8.5. Initial patch by Kevin Walzer. + + +What's New in IDLE 2.7.3? +========================= + +- Issue #14409: IDLE now properly executes commands in the Shell window + when it cannot read the normal config files on startup and + has to use the built-in default key bindings. + There was previously a bug in one of the defaults. + +- Issue #3573: IDLE hangs when passing invalid command line args + (directory(ies) instead of file(s)). + + What's New in IDLE 2.7.2? -======================= +========================= *Release date: 29-May-2011* diff --git a/lib-python/2.7/idlelib/OutputWindow.py b/lib-python/2.7/idlelib/OutputWindow.py --- a/lib-python/2.7/idlelib/OutputWindow.py +++ b/lib-python/2.7/idlelib/OutputWindow.py @@ -57,7 +57,11 @@ # Our own right-button menu rmenu_specs = [ - ("Go to file/line", "<>"), + ("Cut", "<>", "rmenu_check_cut"), + ("Copy", "<>", "rmenu_check_copy"), + ("Paste", "<>", "rmenu_check_paste"), + (None, None, None), + ("Go to file/line", "<>", None), ] file_line_pats = [ diff --git a/lib-python/2.7/idlelib/PyShell.py b/lib-python/2.7/idlelib/PyShell.py --- a/lib-python/2.7/idlelib/PyShell.py +++ b/lib-python/2.7/idlelib/PyShell.py @@ -11,6 +11,7 @@ import threading import traceback import types +import io import linecache from code import InteractiveInterpreter @@ -121,8 +122,13 @@ old_hook() self.io.set_filename_change_hook(filename_changed_hook) - rmenu_specs = [("Set Breakpoint", "<>"), - ("Clear Breakpoint", "<>")] + rmenu_specs = [ + ("Cut", "<>", "rmenu_check_cut"), + ("Copy", "<>", "rmenu_check_copy"), + ("Paste", "<>", "rmenu_check_paste"), + ("Set Breakpoint", "<>", None), + ("Clear Breakpoint", "<>", None) + ] def set_breakpoint(self, lineno): text = self.text @@ -251,8 +257,8 @@ def ranges_to_linenumbers(self, ranges): lines = [] for index in range(0, len(ranges), 2): - lineno = int(float(ranges[index])) - end = int(float(ranges[index+1])) + lineno = int(float(ranges[index].string)) + end = int(float(ranges[index+1].string)) while lineno < end: lines.append(lineno) lineno += 1 @@ -313,6 +319,11 @@ "console": idleConf.GetHighlight(theme, "console"), }) + def removecolors(self): + # Don't remove shell color tags before "iomark" + for tag in self.tagdefs: + self.tag_remove(tag, "iomark", "end") + class ModifiedUndoDelegator(UndoDelegator): "Extend base class: forbid insert/delete before the I/O mark" @@ -417,7 +428,8 @@ except socket.timeout, err: self.display_no_subprocess_error() return None - self.rpcclt.register("stdin", self.tkconsole) + self.rpcclt.register("console", self.tkconsole) + self.rpcclt.register("stdin", self.tkconsole.stdin) self.rpcclt.register("stdout", self.tkconsole.stdout) self.rpcclt.register("stderr", self.tkconsole.stderr) self.rpcclt.register("flist", self.tkconsole.flist) @@ -870,13 +882,14 @@ self.save_stderr = sys.stderr self.save_stdin = sys.stdin from idlelib import IOBinding - self.stdout = PseudoFile(self, "stdout", IOBinding.encoding) - self.stderr = PseudoFile(self, "stderr", IOBinding.encoding) - self.console = PseudoFile(self, "console", IOBinding.encoding) + self.stdin = PseudoInputFile(self, "stdin", IOBinding.encoding) + self.stdout = PseudoOutputFile(self, "stdout", IOBinding.encoding) + self.stderr = PseudoOutputFile(self, "stderr", IOBinding.encoding) + self.console = PseudoOutputFile(self, "console", IOBinding.encoding) if not use_subprocess: sys.stdout = self.stdout sys.stderr = self.stderr - sys.stdin = self + sys.stdin = self.stdin # self.history = self.History(self.text) # @@ -1251,28 +1264,98 @@ if not use_subprocess: raise KeyboardInterrupt -class PseudoFile(object): + def rmenu_check_cut(self): + try: + if self.text.compare('sel.first', '<', 'iomark'): + return 'disabled' + except TclError: # no selection, so the index 'sel.first' doesn't exist + return 'disabled' + return super(PyShell, self).rmenu_check_cut() + + def rmenu_check_paste(self): + if self.text.compare('insert', '<', 'iomark'): + return 'disabled' + return super(PyShell, self).rmenu_check_paste() + +class PseudoFile(io.TextIOBase): def __init__(self, shell, tags, encoding=None): self.shell = shell self.tags = tags self.softspace = 0 - self.encoding = encoding + self._encoding = encoding - def write(self, s): - self.shell.write(s, self.tags) + @property + def encoding(self): + return self._encoding - def writelines(self, lines): - for line in lines: - self.write(line) - - def flush(self): - pass + @property + def name(self): + return '<%s>' % self.tags def isatty(self): return True +class PseudoOutputFile(PseudoFile): + + def writable(self): + return True + + def write(self, s): + if self.closed: + raise ValueError("write to closed file") + if not isinstance(s, (basestring, bytearray)): + raise TypeError('must be string, not ' + type(s).__name__) + 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 = '' + + def readable(self): + return True + + def read(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + result = self._line_buffer + self._line_buffer = '' + if size < 0: + while True: + line = self.shell.readline() + if not line: break + result += line + else: + while len(result) < size: + line = self.shell.readline() + if not line: break + result += line + self._line_buffer = result[size:] + result = result[:size] + return result + + def readline(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + line = self._line_buffer or self.shell.readline() + if size < 0: + size = len(line) + self._line_buffer = line[size:] + return line[:size] + + usage_msg = """\ USAGE: idle [-deins] [-t title] [file]* @@ -1412,8 +1495,10 @@ if enable_edit: if not (cmd or script): - for filename in args: - flist.open(filename) + for filename in args[:]: + if flist.open(filename) is None: + # filename is a directory actually, disconsider it + args.remove(filename) if not args: flist.new() if enable_shell: @@ -1456,7 +1541,8 @@ if tkversionwarning: shell.interp.runcommand(''.join(("print('", tkversionwarning, "')"))) - root.mainloop() + while flist.inversedict: # keep IDLE running while files are open. + root.mainloop() root.destroy() if __name__ == "__main__": diff --git a/lib-python/2.7/idlelib/ReplaceDialog.py b/lib-python/2.7/idlelib/ReplaceDialog.py --- a/lib-python/2.7/idlelib/ReplaceDialog.py +++ b/lib-python/2.7/idlelib/ReplaceDialog.py @@ -2,6 +2,8 @@ from idlelib import SearchEngine from idlelib.SearchDialogBase import SearchDialogBase +import re + def replace(text): root = text._root() @@ -11,6 +13,7 @@ dialog = engine._replacedialog dialog.open(text) + class ReplaceDialog(SearchDialogBase): title = "Replace Dialog" @@ -55,8 +58,22 @@ def default_command(self, event=None): if self.do_find(self.ok): - self.do_replace() - self.do_find(0) + if self.do_replace(): # Only find next match if replace succeeded. + # A bad re can cause a it to fail. + self.do_find(0) + + def _replace_expand(self, m, repl): + """ Helper function for expanding a regular expression + in the replace field, if needed. """ + if self.engine.isre(): + try: + new = m.expand(repl) + except re.error: + self.engine.report_error(repl, 'Invalid Replace Expression') + new = None + else: + new = repl + return new def replace_all(self, event=None): prog = self.engine.getprog() @@ -86,7 +103,9 @@ line, m = res chars = text.get("%d.0" % line, "%d.0" % (line+1)) orig = m.group() - new = m.expand(repl) + new = self._replace_expand(m, repl) + if new is None: + break i, j = m.span() first = "%d.%d" % (line, i) last = "%d.%d" % (line, j) @@ -103,7 +122,6 @@ text.undo_block_stop() if first and last: self.show_hit(first, last) - self.close() def do_find(self, ok=0): if not self.engine.getprog(): @@ -138,7 +156,9 @@ m = prog.match(chars, col) if not prog: return False - new = m.expand(self.replvar.get()) + new = self._replace_expand(m, self.replvar.get()) + if new is None: + return False text.mark_set("insert", first) text.undo_block_start() if m.group(): diff --git a/lib-python/2.7/idlelib/config-extensions.def b/lib-python/2.7/idlelib/config-extensions.def --- a/lib-python/2.7/idlelib/config-extensions.def +++ b/lib-python/2.7/idlelib/config-extensions.def @@ -46,6 +46,8 @@ [ScriptBinding] enable=1 +enable_shell=0 +enable_editor=1 [ScriptBinding_cfgBindings] run-module= check-module= diff --git a/lib-python/2.7/idlelib/configDialog.py b/lib-python/2.7/idlelib/configDialog.py --- a/lib-python/2.7/idlelib/configDialog.py +++ b/lib-python/2.7/idlelib/configDialog.py @@ -183,7 +183,7 @@ text=' Highlighting Theme ') #frameCustom self.textHighlightSample=Text(frameCustom,relief=SOLID,borderwidth=1, - font=('courier',12,''),cursor='hand2',width=21,height=10, + font=('courier',12,''),cursor='hand2',width=21,height=11, takefocus=FALSE,highlightthickness=0,wrap=NONE) text=self.textHighlightSample text.bind('',lambda e: 'break') @@ -832,8 +832,9 @@ fontWeight=tkFont.BOLD else: fontWeight=tkFont.NORMAL - self.editFont.config(size=self.fontSize.get(), - weight=fontWeight,family=fontName) + newFont = (fontName, self.fontSize.get(), fontWeight) + self.labelFontSample.config(font=newFont) + self.textHighlightSample.configure(font=newFont) def SetHighlightTarget(self): if self.highlightTarget.get()=='Cursor': #bg not possible @@ -946,7 +947,7 @@ self.listFontName.select_anchor(currentFontIndex) ##font size dropdown fontSize=idleConf.GetOption('main','EditorWindow','font-size', - default='10') + type='int', default='10') self.optMenuFontSize.SetMenu(('7','8','9','10','11','12','13','14', '16','18','20','22'),fontSize ) ##fontWeight @@ -1032,10 +1033,13 @@ self.autoSave.set(idleConf.GetOption('main', 'General', 'autosave', default=0, type='bool')) #initial window size - self.winWidth.set(idleConf.GetOption('main','EditorWindow','width')) - self.winHeight.set(idleConf.GetOption('main','EditorWindow','height')) + self.winWidth.set(idleConf.GetOption('main','EditorWindow','width', + type='int')) + self.winHeight.set(idleConf.GetOption('main','EditorWindow','height', + type='int')) #initial paragraph reformat size - self.paraWidth.set(idleConf.GetOption('main','FormatParagraph','paragraph')) + self.paraWidth.set(idleConf.GetOption('main','FormatParagraph','paragraph', + type='int')) # default source encoding self.encoding.set(idleConf.GetOption('main', 'EditorWindow', 'encoding', default='none')) diff --git a/lib-python/2.7/idlelib/configHandler.py b/lib-python/2.7/idlelib/configHandler.py --- a/lib-python/2.7/idlelib/configHandler.py +++ b/lib-python/2.7/idlelib/configHandler.py @@ -237,24 +237,39 @@ printed to stderr. """ - if self.userCfg[configType].has_option(section,option): - return self.userCfg[configType].Get(section, option, - type=type, raw=raw) - elif self.defaultCfg[configType].has_option(section,option): - return self.defaultCfg[configType].Get(section, option, - type=type, raw=raw) - else: #returning default, print warning - if warn_on_default: - warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n' - ' problem retrieving configuration option %r\n' - ' from section %r.\n' - ' returning default value: %r\n' % - (option, section, default)) - try: - sys.stderr.write(warning) - except IOError: - pass - return default + try: + if self.userCfg[configType].has_option(section,option): + return self.userCfg[configType].Get(section, option, + type=type, raw=raw) + except ValueError: + warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n' + ' invalid %r value for configuration option %r\n' + ' from section %r: %r\n' % + (type, option, section, + self.userCfg[configType].Get(section, option, + raw=raw))) + try: + sys.stderr.write(warning) + except IOError: + pass + try: + if self.defaultCfg[configType].has_option(section,option): + return self.defaultCfg[configType].Get(section, option, + type=type, raw=raw) + except ValueError: + pass + #returning default, print warning + if warn_on_default: + warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n' + ' problem retrieving configuration option %r\n' + ' from section %r.\n' + ' returning default value: %r\n' % + (option, section, default)) + try: + sys.stderr.write(warning) + except IOError: + pass + return default def SetOption(self, configType, section, option, value): """In user's config file, set section's option to value. @@ -595,7 +610,7 @@ '<>': [''], '<>': [''], '<>': [''], - '<>': [' '], + '<>': ['', ''], '<>': [''], '<>': [''], '<>': [''], diff --git a/lib-python/2.7/idlelib/help.txt b/lib-python/2.7/idlelib/help.txt --- a/lib-python/2.7/idlelib/help.txt +++ b/lib-python/2.7/idlelib/help.txt @@ -80,7 +80,7 @@ Debug Menu (only in Shell window): Go to File/Line -- look around the insert point for a filename - and linenumber, open the file, and show the line + and line number, open the file, and show the line Debugger (toggle) -- Run commands in the shell under the debugger Stack Viewer -- Show the stack traceback of the last exception Auto-open Stack Viewer (toggle) -- Open stack viewer on traceback @@ -92,7 +92,7 @@ Startup Preferences may be set, and Additional Help Sources can be specified. - On MacOS X this menu is not present, use + On OS X this menu is not present, use menu 'IDLE -> Preferences...' instead. --- Code Context -- Open a pane at the top of the edit window which @@ -120,6 +120,24 @@ --- (Additional Help Sources may be added here) +Edit context menu (Right-click / Control-click on OS X in Edit window): + + Cut -- Copy a selection into system-wide clipboard, + then delete the selection + Copy -- Copy selection into system-wide clipboard + Paste -- Insert system-wide clipboard into window + Set Breakpoint -- Sets a breakpoint (when debugger open) + Clear Breakpoint -- Clears the breakpoint on that line + +Shell context menu (Right-click / Control-click on OS X in Shell window): + + Cut -- Copy a selection into system-wide clipboard, + then delete the selection + Copy -- Copy selection into system-wide clipboard + Paste -- Insert system-wide clipboard into window + --- + Go to file/line -- Same as in Debug menu + ** TIPS ** ========== @@ -222,7 +240,7 @@ Alt-p retrieves previous command matching what you have typed. Alt-n retrieves next. - (These are Control-p, Control-n on the Mac) + (These are Control-p, Control-n on OS X) Return while cursor is on a previous command retrieves that command. Expand word is also useful to reduce typing. diff --git a/lib-python/2.7/idlelib/idlever.py b/lib-python/2.7/idlelib/idlever.py --- a/lib-python/2.7/idlelib/idlever.py +++ b/lib-python/2.7/idlelib/idlever.py @@ -1,1 +1,1 @@ -IDLE_VERSION = "2.7.3rc2" +IDLE_VERSION = "2.7.3" diff --git a/lib-python/2.7/idlelib/macosxSupport.py b/lib-python/2.7/idlelib/macosxSupport.py --- a/lib-python/2.7/idlelib/macosxSupport.py +++ b/lib-python/2.7/idlelib/macosxSupport.py @@ -37,17 +37,21 @@ def tkVersionWarning(root): """ Returns a string warning message if the Tk version in use appears to - be one known to cause problems with IDLE. The Apple Cocoa-based Tk 8.5 - that was shipped with Mac OS X 10.6. + be one known to cause problems with IDLE. + 1. Apple Cocoa-based Tk 8.5.7 shipped with Mac OS X 10.6 is unusable. + 2. Apple Cocoa-based Tk 8.5.9 in OS X 10.7 and 10.8 is better but + can still crash unexpectedly. """ if (runningAsOSXApp() and - ('AppKit' in root.tk.call('winfo', 'server', '.')) and - (root.tk.call('info', 'patchlevel') == '8.5.7') ): - return (r"WARNING: The version of Tcl/Tk (8.5.7) in use may" + ('AppKit' in root.tk.call('winfo', 'server', '.')) ): + patchlevel = root.tk.call('info', 'patchlevel') + if patchlevel not in ('8.5.7', '8.5.9'): + return False + return (r"WARNING: The version of Tcl/Tk ({0}) in use may" r" be unstable.\n" r"Visit http://www.python.org/download/mac/tcltk/" - r" for current information.") + r" for current information.".format(patchlevel)) else: return False diff --git a/lib-python/2.7/idlelib/run.py b/lib-python/2.7/idlelib/run.py --- a/lib-python/2.7/idlelib/run.py +++ b/lib-python/2.7/idlelib/run.py @@ -1,4 +1,5 @@ import sys +import io import linecache import time import socket @@ -14,6 +15,8 @@ from idlelib import RemoteObjectBrowser from idlelib import StackViewer from idlelib import rpc +from idlelib import PyShell +from idlelib import IOBinding import __main__ @@ -248,19 +251,19 @@ quitting = True thread.interrupt_main() - class MyHandler(rpc.RPCHandler): def handle(self): """Override base method""" executive = Executive(self) self.register("exec", executive) - sys.stdin = self.console = self.get_remote_proxy("stdin") - sys.stdout = self.get_remote_proxy("stdout") - sys.stderr = self.get_remote_proxy("stderr") - from idlelib import IOBinding - sys.stdin.encoding = sys.stdout.encoding = \ - sys.stderr.encoding = IOBinding.encoding + self.console = self.get_remote_proxy("console") + sys.stdin = PyShell.PseudoInputFile(self.console, "stdin", + IOBinding.encoding) + sys.stdout = PyShell.PseudoOutputFile(self.console, "stdout", + IOBinding.encoding) + sys.stderr = PyShell.PseudoOutputFile(self.console, "stderr", + IOBinding.encoding) self.interp = self.get_remote_proxy("interp") rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) @@ -298,11 +301,14 @@ exec code in self.locals finally: interruptable = False + except SystemExit: + # Scripts that raise SystemExit should just + # return to the interactive prompt + pass except: self.usr_exc_info = sys.exc_info() if quitting: exit() - # even print a user code SystemExit exception, continue print_exception() jit = self.rpchandler.console.getvar("<>") if jit: diff --git a/lib-python/2.7/io.py b/lib-python/2.7/io.py --- a/lib-python/2.7/io.py +++ b/lib-python/2.7/io.py @@ -4,7 +4,7 @@ At the top of the I/O hierarchy is the abstract base class IOBase. It defines the basic interface to a stream. Note, however, that there is no separation between reading and writing to streams; implementations are -allowed to throw an IOError if they do not support a given operation. +allowed to raise an IOError if they do not support a given operation. Extending IOBase is RawIOBase which deals simply with the reading and writing of raw bytes to a stream. FileIO subclasses RawIOBase to provide @@ -34,15 +34,6 @@ """ # New I/O library conforming to PEP 3116. -# XXX edge cases when switching between reading/writing -# XXX need to support 1 meaning line-buffered -# XXX whenever an argument is None, use the default value -# XXX read/write ops should check readable/writable -# XXX buffered readinto should work with arbitrary buffer objects -# XXX use incremental encoder for text output, at least for UTF-16 and UTF-8-SIG -# XXX check writable, readable and seekable in appropriate places - - __author__ = ("Guido van Rossum , " "Mike Verdone , " "Mark Russell , " diff --git a/lib-python/2.7/json/__init__.py b/lib-python/2.7/json/__init__.py --- a/lib-python/2.7/json/__init__.py +++ b/lib-python/2.7/json/__init__.py @@ -37,8 +37,8 @@ Pretty printing:: >>> import json - >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4) - >>> print '\n'.join([l.rstrip() for l in s.splitlines()]) + >>> print json.dumps({'4': 5, '6': 7}, sort_keys=True, + ... indent=4, separators=(',', ': ')) { "4": 5, "6": 7 @@ -95,7 +95,7 @@ "json": "obj" } $ echo '{ 1.2:3.4}' | python -m json.tool - Expecting property name: line 1 column 2 (char 2) + Expecting property name enclosed in double quotes: line 1 column 3 (char 2) """ __version__ = '2.0.9' __all__ = [ @@ -121,7 +121,7 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', default=None, **kw): + encoding='utf-8', default=None, sort_keys=False, **kw): """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a ``.write()``-supporting file-like object). @@ -129,11 +129,14 @@ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) will be skipped instead of raising a ``TypeError``. - If ``ensure_ascii`` is false, then the some chunks written to ``fp`` - may be ``unicode`` instances, subject to normal Python ``str`` to - ``unicode`` coercion rules. Unless ``fp.write()`` explicitly - understands ``unicode`` (as in ``codecs.getwriter()``) this is likely - to cause an error. + If ``ensure_ascii`` is true (the default), all non-ASCII characters in the + output are escaped with ``\uXXXX`` sequences, and the result is a ``str`` + instance consisting of ASCII characters only. If ``ensure_ascii`` is + ``False``, some chunks written to ``fp`` may be ``unicode`` instances. + This usually happens because the input contains unicode strings or the + ``encoding`` parameter is used. Unless ``fp.write()`` explicitly + understands ``unicode`` (as in ``codecs.getwriter``) this is likely to + cause an error. If ``check_circular`` is false, then the circular reference check for container types will be skipped and a circular reference will @@ -147,7 +150,9 @@ If ``indent`` is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. ``None`` is the most compact - representation. + representation. Since the default item separator is ``', '``, the + output might include trailing whitespace when ``indent`` is specified. + You can use ``separators=(',', ': ')`` to avoid this. If ``separators`` is an ``(item_separator, dict_separator)`` tuple then it will be used instead of the default ``(', ', ': ')`` separators. @@ -158,6 +163,9 @@ ``default(obj)`` is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. + If *sort_keys* is ``True`` (default: ``False``), then the output of + dictionaries will be sorted by key. + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the ``.default()`` method to serialize additional types), specify it with the ``cls`` kwarg; otherwise ``JSONEncoder`` is used. @@ -167,7 +175,7 @@ if (not skipkeys and ensure_ascii and check_circular and allow_nan and cls is None and indent is None and separators is None and - encoding == 'utf-8' and default is None and not kw): + encoding == 'utf-8' and default is None and not sort_keys and not kw): iterable = _default_encoder.iterencode(obj) else: if cls is None: @@ -175,7 +183,7 @@ iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, indent=indent, separators=separators, encoding=encoding, - default=default, **kw).iterencode(obj) + default=default, sort_keys=sort_keys, **kw).iterencode(obj) # could accelerate with writelines in some versions of Python, at # a debuggability cost for chunk in iterable: @@ -184,16 +192,15 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', default=None, **kw): + encoding='utf-8', default=None, sort_keys=False, **kw): """Serialize ``obj`` to a JSON formatted ``str``. If ``skipkeys`` is false then ``dict`` keys that are not basic types (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) will be skipped instead of raising a ``TypeError``. - If ``ensure_ascii`` is false, then the return value will be a - ``unicode`` instance subject to normal Python ``str`` to ``unicode`` - coercion rules instead of being escaped to an ASCII ``str``. + If ``ensure_ascii`` is false, all non-ASCII characters are not escaped, and + the return value may be a ``unicode`` instance. See ``dump`` for details. If ``check_circular`` is false, then the circular reference check for container types will be skipped and a circular reference will @@ -207,7 +214,9 @@ If ``indent`` is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. ``None`` is the most compact - representation. + representation. Since the default item separator is ``', '``, the + output might include trailing whitespace when ``indent`` is specified. + You can use ``separators=(',', ': ')`` to avoid this. If ``separators`` is an ``(item_separator, dict_separator)`` tuple then it will be used instead of the default ``(', ', ': ')`` separators. @@ -218,6 +227,9 @@ ``default(obj)`` is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. + If *sort_keys* is ``True`` (default: ``False``), then the output of + dictionaries will be sorted by key. + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the ``.default()`` method to serialize additional types), specify it with the ``cls`` kwarg; otherwise ``JSONEncoder`` is used. @@ -227,7 +239,7 @@ if (not skipkeys and ensure_ascii and check_circular and allow_nan and cls is None and indent is None and separators is None and - encoding == 'utf-8' and default is None and not kw): + encoding == 'utf-8' and default is None and not sort_keys and not kw): return _default_encoder.encode(obj) if cls is None: cls = JSONEncoder @@ -235,7 +247,7 @@ skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, indent=indent, separators=separators, encoding=encoding, default=default, - **kw).encode(obj) + sort_keys=sort_keys, **kw).encode(obj) _default_decoder = JSONDecoder(encoding=None, object_hook=None, diff --git a/lib-python/2.7/json/decoder.py b/lib-python/2.7/json/decoder.py --- a/lib-python/2.7/json/decoder.py +++ b/lib-python/2.7/json/decoder.py @@ -27,7 +27,7 @@ def linecol(doc, pos): lineno = doc.count('\n', 0, pos) + 1 if lineno == 1: - colno = pos + colno = pos + 1 else: colno = pos - doc.rindex('\n', 0, pos) return lineno, colno @@ -169,7 +169,8 @@ pairs = object_hook(pairs) return pairs, end + 1 elif nextchar != '"': - raise ValueError(errmsg("Expecting property name", s, end)) + raise ValueError(errmsg( + "Expecting property name enclosed in double quotes", s, end)) end += 1 while True: key, end = scanstring(s, end, encoding, strict) @@ -179,8 +180,7 @@ if s[end:end + 1] != ':': end = _w(s, end).end() if s[end:end + 1] != ':': - raise ValueError(errmsg("Expecting : delimiter", s, end)) - + raise ValueError(errmsg("Expecting ':' delimiter", s, end)) end += 1 try: @@ -209,7 +209,7 @@ if nextchar == '}': break elif nextchar != ',': - raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) + raise ValueError(errmsg("Expecting ',' delimiter", s, end - 1)) try: nextchar = s[end] @@ -224,8 +224,8 @@ end += 1 if nextchar != '"': - raise ValueError(errmsg("Expecting property name", s, end - 1)) - + raise ValueError(errmsg( + "Expecting property name enclosed in double quotes", s, end - 1)) if object_pairs_hook is not None: result = object_pairs_hook(pairs) return result, end @@ -259,8 +259,7 @@ if nextchar == ']': break elif nextchar != ',': - raise ValueError(errmsg("Expecting , delimiter", s, end)) - + raise ValueError(errmsg("Expecting ',' delimiter", s, end)) try: if s[end] in _ws: end += 1 diff --git a/lib-python/2.7/json/encoder.py b/lib-python/2.7/json/encoder.py --- a/lib-python/2.7/json/encoder.py +++ b/lib-python/2.7/json/encoder.py @@ -27,8 +27,7 @@ ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i)) #ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) -# Assume this produces an infinity on all machines (probably not guaranteed) -INFINITY = float('1e66666') +INFINITY = float('inf') FLOAT_REPR = repr def encode_basestring(s): @@ -108,9 +107,12 @@ encoding of keys that are not str, int, long, float or None. If skipkeys is True, such items are simply skipped. - If ensure_ascii is true, the output is guaranteed to be str - objects with all incoming unicode characters escaped. If - ensure_ascii is false, the output will be unicode object. + If *ensure_ascii* is true (the default), all non-ASCII + characters in the output are escaped with \uXXXX sequences, + and the results are str instances consisting of ASCII + characters only. If ensure_ascii is False, a result may be a + unicode instance. This usually happens if the input contains + unicode strings or the *encoding* parameter is used. If check_circular is true, then lists, dicts, and custom encoded objects will be checked for circular references during encoding to @@ -129,7 +131,10 @@ If indent is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. - None is the most compact representation. + None is the most compact representation. Since the default + item separator is ', ', the output might include trailing + whitespace when indent is specified. You can use + separators=(',', ': ') to avoid this. If specified, separators should be a (item_separator, key_separator) tuple. The default is (', ', ': '). To get the most compact JSON diff --git a/lib-python/2.7/json/tests/test_decode.py b/lib-python/2.7/json/tests/test_decode.py --- a/lib-python/2.7/json/tests/test_decode.py +++ b/lib-python/2.7/json/tests/test_decode.py @@ -45,6 +45,15 @@ object_hook=lambda x: None), OrderedDict(p)) + def test_extra_data(self): + s = '[1, 2, 3]5' + msg = 'Extra data' + self.assertRaisesRegexp(ValueError, msg, self.loads, s) + + def test_invalid_escape(self): + s = '["abc\\y"]' + msg = 'escape' + self.assertRaisesRegexp(ValueError, msg, self.loads, s) class TestPyDecode(TestDecode, PyTest): pass class TestCDecode(TestDecode, CTest): pass diff --git a/lib-python/2.7/json/tests/test_dump.py b/lib-python/2.7/json/tests/test_dump.py --- a/lib-python/2.7/json/tests/test_dump.py +++ b/lib-python/2.7/json/tests/test_dump.py @@ -19,5 +19,14 @@ {2: 3.0, 4.0: 5L, False: 1, 6L: True}, sort_keys=True), '{"false": 1, "2": 3.0, "4.0": 5, "6": true}') + # Issue 16228: Crash on encoding resized list + def test_encode_mutated(self): + a = [object()] * 10 + def crasher(obj): + del a[-1] + self.assertEqual(self.dumps(a, default=crasher), + '[null, null, null, null, null]') + + class TestPyDump(TestDump, PyTest): pass class TestCDump(TestDump, CTest): pass diff --git a/lib-python/2.7/json/tests/test_fail.py b/lib-python/2.7/json/tests/test_fail.py --- a/lib-python/2.7/json/tests/test_fail.py +++ b/lib-python/2.7/json/tests/test_fail.py @@ -1,13 +1,13 @@ from json.tests import PyTest, CTest -# Fri Dec 30 18:57:26 2005 +# 2007-10-05 JSONDOCS = [ # http://json.org/JSON_checker/test/fail1.json '"A JSON payload should be an object or array, not a string."', # http://json.org/JSON_checker/test/fail2.json '["Unclosed array"', # http://json.org/JSON_checker/test/fail3.json - '{unquoted_key: "keys must be quoted}', + '{unquoted_key: "keys must be quoted"}', # http://json.org/JSON_checker/test/fail4.json '["extra comma",]', # http://json.org/JSON_checker/test/fail5.json @@ -33,7 +33,7 @@ # http://json.org/JSON_checker/test/fail15.json '["Illegal backslash escape: \\x15"]', # http://json.org/JSON_checker/test/fail16.json - '["Illegal backslash escape: \\\'"]', + '[\\naked]', # http://json.org/JSON_checker/test/fail17.json '["Illegal backslash escape: \\017"]', # http://json.org/JSON_checker/test/fail18.json @@ -50,6 +50,24 @@ '["Bad value", truth]', # http://json.org/JSON_checker/test/fail24.json "['single quote']", + # http://json.org/JSON_checker/test/fail25.json + '["\ttab\tcharacter\tin\tstring\t"]', + # http://json.org/JSON_checker/test/fail26.json + '["tab\\ character\\ in\\ string\\ "]', + # http://json.org/JSON_checker/test/fail27.json + '["line\nbreak"]', + # http://json.org/JSON_checker/test/fail28.json + '["line\\\nbreak"]', + # http://json.org/JSON_checker/test/fail29.json + '[0e]', + # http://json.org/JSON_checker/test/fail30.json + '[0e+]', + # http://json.org/JSON_checker/test/fail31.json + '[0e+-1]', + # http://json.org/JSON_checker/test/fail32.json + '{"Comma instead if closing brace": true,', + # http://json.org/JSON_checker/test/fail33.json + '["mismatch"}', # http://code.google.com/p/simplejson/issues/detail?id=3 u'["A\u001FZ control characters in string"]', ] diff --git a/lib-python/2.7/json/tests/test_float.py b/lib-python/2.7/json/tests/test_float.py --- a/lib-python/2.7/json/tests/test_float.py +++ b/lib-python/2.7/json/tests/test_float.py @@ -17,6 +17,21 @@ self.assertEqual(self.loads(self.dumps(num)), num) self.assertEqual(self.loads(unicode(self.dumps(num))), num) + def test_out_of_range(self): + self.assertEqual(self.loads('[23456789012E666]'), [float('inf')]) + self.assertEqual(self.loads('[-23456789012E666]'), [float('-inf')]) + + def test_allow_nan(self): + for val in (float('inf'), float('-inf'), float('nan')): + out = self.dumps([val]) + if val == val: # inf + self.assertEqual(self.loads(out), [val]) + else: # nan + res = self.loads(out) + self.assertEqual(len(res), 1) + self.assertNotEqual(res[0], res[0]) + self.assertRaises(ValueError, self.dumps, [val], allow_nan=False) + class TestPyFloat(TestFloat, PyTest): pass class TestCFloat(TestFloat, CTest): pass diff --git a/lib-python/2.7/json/tests/test_pass1.py b/lib-python/2.7/json/tests/test_pass1.py --- a/lib-python/2.7/json/tests/test_pass1.py +++ b/lib-python/2.7/json/tests/test_pass1.py @@ -17,7 +17,7 @@ "real": -9876.543210, "e": 0.123456789e-12, "E": 1.234567890E+34, - "": 23456789012E666, + "": 23456789012E66, "zero": 0, "one": 1, "space": " ", @@ -28,6 +28,7 @@ "alpha": "abcdefghijklmnopqrstuvwyz", "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", "digit": "0123456789", + "0123456789": "digit", "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", "true": true, @@ -43,8 +44,7 @@ , -4 , 5 , 6 ,7 ], - "compact": [1,2,3,4,5,6,7], +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", "quotes": "" \u0022 %22 0x22 034 "", "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" @@ -55,9 +55,11 @@ 99.44 , -1066 - - +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 ,"rosebud"] ''' @@ -67,12 +69,6 @@ res = self.loads(JSON) out = self.dumps(res) self.assertEqual(res, self.loads(out)) - try: - self.dumps(res, allow_nan=False) - except ValueError: - pass - else: - self.fail("23456789012E666 should be out of range") class TestPyPass1(TestPass1, PyTest): pass diff --git a/lib-python/2.7/json/tool.py b/lib-python/2.7/json/tool.py --- a/lib-python/2.7/json/tool.py +++ b/lib-python/2.7/json/tool.py @@ -7,7 +7,7 @@ "json": "obj" } $ echo '{ 1.2:3.4}' | python -m json.tool - Expecting property name: line 1 column 2 (char 2) + Expecting property name enclosed in double quotes: line 1 column 3 (char 2) """ import sys @@ -25,12 +25,15 @@ outfile = open(sys.argv[2], 'wb') else: raise SystemExit(sys.argv[0] + " [infile [outfile]]") - try: - obj = json.load(infile) - except ValueError, e: - raise SystemExit(e) - json.dump(obj, outfile, sort_keys=True, indent=4) - outfile.write('\n') + with infile: + try: + obj = json.load(infile) + except ValueError, e: + raise SystemExit(e) + with outfile: + json.dump(obj, outfile, sort_keys=True, + indent=4, separators=(',', ': ')) + outfile.write('\n') if __name__ == '__main__': diff --git a/lib-python/2.7/lib-tk/Tkinter.py b/lib-python/2.7/lib-tk/Tkinter.py --- a/lib-python/2.7/lib-tk/Tkinter.py +++ b/lib-python/2.7/lib-tk/Tkinter.py @@ -41,6 +41,7 @@ TclError = _tkinter.TclError from types import * from Tkconstants import * +import re wantobjects = 1 @@ -58,6 +59,37 @@ except AttributeError: _tkinter.deletefilehandler = None +_magic_re = re.compile(r'([\\{}])') +_space_re = re.compile(r'([\s])') + +def _join(value): + """Internal function.""" + return ' '.join(map(_stringify, value)) + +def _stringify(value): + """Internal function.""" + if isinstance(value, (list, tuple)): + if len(value) == 1: + value = _stringify(value[0]) + if value[0] == '{': + value = '{%s}' % value + else: + value = '{%s}' % _join(value) + else: + if isinstance(value, basestring): + value = unicode(value) + else: + value = str(value) + if not value: + value = '{}' + elif _magic_re.search(value): + # add '\' before special characters and spaces + value = _magic_re.sub(r'\\\1', value) + value = _space_re.sub(r'\\\1', value) + elif value[0] == '"' or _space_re.search(value): + value = '{%s}' % value + return value + def _flatten(tuple): """Internal function.""" res = () @@ -154,8 +186,12 @@ """Internal function.""" pass -def _exit(code='0'): - """Internal function. Calling it will throw the exception SystemExit.""" +def _exit(code=0): + """Internal function. Calling it will raise the exception SystemExit.""" + try: + code = int(code) + except ValueError: + pass raise SystemExit, code _varnum = 0 @@ -534,12 +570,19 @@ The type keyword specifies the form in which the data is to be returned and should be an atom name such as STRING - or FILE_NAME. Type defaults to STRING. + or FILE_NAME. Type defaults to STRING, except on X11, where the default + is to try UTF8_STRING and fall back to STRING. This command is equivalent to: selection_get(CLIPBOARD) """ + if 'type' not in kw and self._windowingsystem == 'x11': + try: + kw['type'] = 'UTF8_STRING' + return self.tk.call(('clipboard', 'get') + self._options(kw)) + except TclError: + del kw['type'] return self.tk.call(('clipboard', 'get') + self._options(kw)) def clipboard_clear(self, **kw): @@ -621,8 +664,16 @@ A keyword parameter selection specifies the name of the selection and defaults to PRIMARY. A keyword parameter displayof specifies a widget on the display - to use.""" + to use. A keyword parameter type specifies the form of data to be + fetched, defaulting to STRING except on X11, where UTF8_STRING is tried + before STRING.""" if 'displayof' not in kw: kw['displayof'] = self._w + if 'type' not in kw and self._windowingsystem == 'x11': + try: + kw['type'] = 'UTF8_STRING' + return self.tk.call(('selection', 'get') + self._options(kw)) + except TclError: + del kw['type'] return self.tk.call(('selection', 'get') + self._options(kw)) def selection_handle(self, command, **kw): """Specify a function COMMAND to call if the X @@ -1037,6 +1088,15 @@ if displayof is None: return ('-displayof', self._w) return () + @property + def _windowingsystem(self): + """Internal function.""" + try: + return self._root()._windowingsystem_cached + except AttributeError: + ws = self._root()._windowingsystem_cached = \ + self.tk.call('tk', 'windowingsystem') + return ws def _options(self, cnf, kw = None): """Internal function.""" if kw: @@ -1058,7 +1118,7 @@ nv.append('%d' % item) else: # format it to proper Tcl code if it contains space - nv.append(('{%s}' if ' ' in item else '%s') % item) + nv.append(_stringify(item)) else: v = ' '.join(nv) res = res + ('-'+k, v) @@ -1685,7 +1745,9 @@ self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use) if useTk: self._loadtk() - self.readprofile(baseName, className) + if not sys.flags.ignore_environment: + # Issue #16248: Honor the -E flag to avoid code injection. + self.readprofile(baseName, className) def loadtk(self): if not self._tkloaded: self.tk.loadtk() diff --git a/lib-python/2.7/lib-tk/test/test_ttk/test_functions.py b/lib-python/2.7/lib-tk/test/test_ttk/test_functions.py --- a/lib-python/2.7/lib-tk/test/test_ttk/test_functions.py +++ b/lib-python/2.7/lib-tk/test/test_ttk/test_functions.py @@ -50,13 +50,17 @@ ttk._format_optdict({'test': {'left': 'as is'}}), {'-test': {'left': 'as is'}}) - # check script formatting and untouched value(s) + # check script formatting check_against( ttk._format_optdict( - {'test': [1, -1, '', '2m', 0], 'nochange1': 3, - 'nochange2': 'abc def'}, script=True), - {'-test': '{1 -1 {} 2m 0}', '-nochange1': 3, - '-nochange2': 'abc def' }) + {'test': [1, -1, '', '2m', 0], 'test2': 3, + 'test3': '', 'test4': 'abc def', + 'test5': '"abc"', 'test6': '{}', + 'test7': '} -spam {'}, script=True), + {'-test': '{1 -1 {} 2m 0}', '-test2': '3', + '-test3': '{}', '-test4': '{abc def}', + '-test5': '{"abc"}', '-test6': r'\{\}', + '-test7': r'\}\ -spam\ \{'}) opts = {u'??????': True, u'??': False} orig_opts = opts.copy() @@ -70,6 +74,32 @@ ttk._format_optdict( {'option': ('one two', 'three')}), {'-option': '{one two} three'}) + check_against( + ttk._format_optdict( + {'option': ('one\ttwo', 'three')}), + {'-option': '{one\ttwo} three'}) + + # passing empty strings inside a tuple/list + check_against( + ttk._format_optdict( + {'option': ('', 'one')}), + {'-option': '{} one'}) + + # passing values with braces inside a tuple/list + check_against( + ttk._format_optdict( + {'option': ('one} {two', 'three')}), + {'-option': r'one\}\ \{two three'}) + + # passing quoted strings inside a tuple/list + check_against( + ttk._format_optdict( + {'option': ('"one"', 'two')}), + {'-option': '{"one"} two'}) + check_against( + ttk._format_optdict( + {'option': ('{one}', 'two')}), + {'-option': r'\{one\} two'}) # ignore an option amount_opts = len(ttk._format_optdict(opts, ignore=(u'??'))) // 2 diff --git a/lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py b/lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py --- a/lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py +++ b/lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py @@ -188,6 +188,14 @@ self.combo.configure(values=[1, '', 2]) self.assertEqual(self.combo['values'], ('1', '', '2')) + # testing values with spaces + self.combo['values'] = ['a b', 'a\tb', 'a\nb'] + self.assertEqual(self.combo['values'], ('a b', 'a\tb', 'a\nb')) + + # testing values with special characters + self.combo['values'] = [r'a\tb', '"a"', '} {'] + self.assertEqual(self.combo['values'], (r'a\tb', '"a"', '} {')) + # out of range self.assertRaises(Tkinter.TclError, self.combo.current, len(self.combo['values'])) diff --git a/lib-python/2.7/lib-tk/tkSimpleDialog.py b/lib-python/2.7/lib-tk/tkSimpleDialog.py --- a/lib-python/2.7/lib-tk/tkSimpleDialog.py +++ b/lib-python/2.7/lib-tk/tkSimpleDialog.py @@ -200,7 +200,7 @@ self.entry = Entry(master, name="entry") self.entry.grid(row=1, padx=5, sticky=W+E) - if self.initialvalue: + if self.initialvalue is not None: self.entry.insert(0, self.initialvalue) self.entry.select_range(0, END) diff --git a/lib-python/2.7/lib-tk/ttk.py b/lib-python/2.7/lib-tk/ttk.py --- a/lib-python/2.7/lib-tk/ttk.py +++ b/lib-python/2.7/lib-tk/ttk.py @@ -26,8 +26,7 @@ "tclobjs_to_py", "setup_master"] import Tkinter - -_flatten = Tkinter._flatten +from Tkinter import _flatten, _join, _stringify # Verify if Tk is new enough to not need the Tile package _REQUIRE_TILE = True if Tkinter.TkVersion < 8.5 else False @@ -47,39 +46,56 @@ master.tk.eval('package require tile') # TclError may be raised here master._tile_loaded = True +def _format_optvalue(value, script=False): + """Internal function.""" + if script: + # if caller passes a Tcl script to tk.call, all the values need to + # be grouped into words (arguments to a command in Tcl dialect) + value = _stringify(value) + elif isinstance(value, (list, tuple)): + value = _join(value) + return value + def _format_optdict(optdict, script=False, ignore=None): """Formats optdict to a tuple to pass it to tk.call. E.g. (script=False): {'foreground': 'blue', 'padding': [1, 2, 3, 4]} returns: ('-foreground', 'blue', '-padding', '1 2 3 4')""" - format = "%s" if not script else "{%s}" opts = [] for opt, value in optdict.iteritems(): - if ignore and opt in ignore: - continue + if not ignore or opt not in ignore: + opts.append("-%s" % opt) + if value is not None: + opts.append(_format_optvalue(value, script)) - if isinstance(value, (list, tuple)): - v = [] - for val in value: - if isinstance(val, basestring): - v.append(unicode(val) if val else '{}') - else: - v.append(str(val)) + return _flatten(opts) - # format v according to the script option, but also check for - # space in any value in v in order to group them correctly - value = format % ' '.join( - ('{%s}' if ' ' in val else '%s') % val for val in v) - - if script and value == '': - value = '{}' # empty string in Python is equivalent to {} in Tcl - - opts.append(("-%s" % opt, value)) - - # Remember: _flatten skips over None - return _flatten(opts) +def _mapdict_values(items): + # each value in mapdict is expected to be a sequence, where each item + # is another sequence containing a state (or several) and a value + # E.g. (script=False): + # [('active', 'selected', 'grey'), ('focus', [1, 2, 3, 4])] + # returns: + # ['active selected', 'grey', 'focus', [1, 2, 3, 4]] + opt_val = [] + for item in items: + state = item[:-1] + val = item[-1] + # hacks for bakward compatibility + state[0] # raise IndexError if empty + if len(state) == 1: + # if it is empty (something that evaluates to False), then + # format it to Tcl code to denote the "normal" state + state = state[0] or '' + else: + # group multiple states + state = ' '.join(state) # raise TypeError if not str + opt_val.append(state) + if val is not None: + opt_val.append(val) + return opt_val def _format_mapdict(mapdict, script=False): """Formats mapdict to pass it to tk.call. @@ -90,32 +106,11 @@ returns: ('-expand', '{active selected} grey focus {1, 2, 3, 4}')""" - # if caller passes a Tcl script to tk.call, all the values need to - # be grouped into words (arguments to a command in Tcl dialect) - format = "%s" if not script else "{%s}" opts = [] for opt, value in mapdict.iteritems(): - - opt_val = [] - # each value in mapdict is expected to be a sequence, where each item - # is another sequence containing a state (or several) and a value - for statespec in value: - state, val = statespec[:-1], statespec[-1] - - if len(state) > 1: # group multiple states - state = "{%s}" % ' '.join(state) - else: # single state - # if it is empty (something that evaluates to False), then - # format it to Tcl code to denote the "normal" state - state = state[0] or '{}' - - if isinstance(val, (list, tuple)): # val needs to be grouped - val = "{%s}" % ' '.join(map(str, val)) - - opt_val.append("%s %s" % (state, val)) - - opts.append(("-%s" % opt, format % ' '.join(opt_val))) + opts.extend(("-%s" % opt, + _format_optvalue(_mapdict_values(value), script))) return _flatten(opts) @@ -129,7 +124,7 @@ iname = args[0] # next args, if any, are statespec/value pairs which is almost # a mapdict, but we just need the value - imagespec = _format_mapdict({None: args[1:]})[1] + imagespec = _join(_mapdict_values(args[1:])) spec = "%s %s" % (iname, imagespec) else: @@ -138,7 +133,7 @@ # themed styles on Windows XP and Vista. # Availability: Tk 8.6, Windows XP and Vista. class_name, part_id = args[:2] - statemap = _format_mapdict({None: args[2:]})[1] + statemap = _join(_mapdict_values(args[2:])) spec = "%s %s %s" % (class_name, part_id, statemap) opts = _format_optdict(kw, script) @@ -148,11 +143,11 @@ # otherwise it will clone {} (empty element) spec = args[0] # theme name if len(args) > 1: # elementfrom specified - opts = (args[1], ) + opts = (_format_optvalue(args[1], script),) if script: spec = '{%s}' % spec - opts = ' '.join(map(str, opts)) + opts = ' '.join(opts) return spec, opts @@ -189,7 +184,7 @@ for layout_elem in layout: elem, opts = layout_elem opts = opts or {} - fopts = ' '.join(map(str, _format_optdict(opts, True, "children"))) + fopts = ' '.join(_format_optdict(opts, True, ("children",))) head = "%s%s%s" % (' ' * indent, elem, (" %s" % fopts) if fopts else '') if "children" in opts: @@ -215,11 +210,11 @@ for name, opts in settings.iteritems(): # will format specific keys according to Tcl code if opts.get('configure'): # format 'configure' - s = ' '.join(map(unicode, _format_optdict(opts['configure'], True))) + s = ' '.join(_format_optdict(opts['configure'], True)) script.append("ttk::style configure %s %s;" % (name, s)) if opts.get('map'): # format 'map' - s = ' '.join(map(unicode, _format_mapdict(opts['map'], True))) + s = ' '.join(_format_mapdict(opts['map'], True)) script.append("ttk::style map %s %s;" % (name, s)) if 'layout' in opts: # format 'layout' which may be empty @@ -706,30 +701,9 @@ exportselection, justify, height, postcommand, state, textvariable, values, width """ - # The "values" option may need special formatting, so leave to - # _format_optdict the responsibility to format it - if "values" in kw: - kw["values"] = _format_optdict({'v': kw["values"]})[1] - Entry.__init__(self, master, "ttk::combobox", **kw) - def __setitem__(self, item, value): - if item == "values": - value = _format_optdict({item: value})[1] - - Entry.__setitem__(self, item, value) - - - def configure(self, cnf=None, **kw): - """Custom Combobox configure, created to properly format the values - option.""" - if "values" in kw: - kw["values"] = _format_optdict({'v': kw["values"]})[1] - - return Entry.configure(self, cnf, **kw) - - def current(self, newindex=None): """If newindex is supplied, sets the combobox value to the element at position newindex in the list of values. Otherwise, @@ -1253,7 +1227,7 @@ def exists(self, item): - """Returns True if the specified item is present in the three, + """Returns True if the specified item is present in the tree, False otherwise.""" return bool(self.tk.call(self._w, "exists", item)) diff --git a/lib-python/2.7/lib2to3/fixer_util.py b/lib-python/2.7/lib2to3/fixer_util.py --- a/lib-python/2.7/lib2to3/fixer_util.py +++ b/lib-python/2.7/lib2to3/fixer_util.py @@ -165,7 +165,7 @@ consuming_calls = set(["sorted", "list", "set", "any", "all", "tuple", "sum", - "min", "max"]) + "min", "max", "enumerate"]) def attr_chain(obj, attr): """Follow an attribute chain. @@ -192,14 +192,14 @@ p1 = """ power< ( 'iter' | 'list' | 'tuple' | 'sorted' | 'set' | 'sum' | - 'any' | 'all' | (any* trailer< '.' 'join' >) ) + 'any' | 'all' | 'enumerate' | (any* trailer< '.' 'join' >) ) trailer< '(' node=any ')' > any* > """ p2 = """ power< - 'sorted' + ( 'sorted' | 'enumerate' ) trailer< '(' arglist ')' > any* > @@ -207,14 +207,14 @@ pats_built = False def in_special_context(node): """ Returns true if node is in an environment where all that is required - of it is being itterable (ie, it doesn't matter if it returns a list - or an itterator). + of it is being iterable (ie, it doesn't matter if it returns a list + or an iterator). See test_map_nochange in test_fixers.py for some examples and tests. """ global p0, p1, p2, pats_built if not pats_built: + p0 = patcomp.compile_pattern(p0) p1 = patcomp.compile_pattern(p1) - p0 = patcomp.compile_pattern(p0) p2 = patcomp.compile_pattern(p2) pats_built = True patterns = [p0, p1, p2] @@ -274,9 +274,9 @@ """Find the top level namespace.""" # Scamper up to the top level namespace while node.type != syms.file_input: - assert node.parent, "Tree is insane! root found before "\ - "file_input node was found." node = node.parent + if not node: + raise ValueError("root found before file_input node was found.") return node def does_tree_import(package, name, node): diff --git a/lib-python/2.7/lib2to3/pgen2/driver.py b/lib-python/2.7/lib2to3/pgen2/driver.py --- a/lib-python/2.7/lib2to3/pgen2/driver.py +++ b/lib-python/2.7/lib2to3/pgen2/driver.py @@ -138,3 +138,20 @@ if not os.path.exists(b): return True return os.path.getmtime(a) >= os.path.getmtime(b) + + +def main(*args): + """Main program, when run as a script: produce grammar pickle files. + + Calls load_grammar for each argument, a path to a grammar text file. + """ + if not args: + args = sys.argv[1:] + logging.basicConfig(level=logging.INFO, stream=sys.stdout, + format='%(message)s') + for gt in args: + load_grammar(gt, save=True, force=True) + return True + +if __name__ == "__main__": + sys.exit(int(not main())) diff --git a/lib-python/2.7/lib2to3/refactor.py b/lib-python/2.7/lib2to3/refactor.py --- a/lib-python/2.7/lib2to3/refactor.py +++ b/lib-python/2.7/lib2to3/refactor.py @@ -445,7 +445,7 @@ try: find_root(node) - except AssertionError: + except ValueError: # this node has been cut off from a # previous transformation ; skip continue diff --git a/lib-python/2.7/lib2to3/tests/test_fixers.py b/lib-python/2.7/lib2to3/tests/test_fixers.py --- a/lib-python/2.7/lib2to3/tests/test_fixers.py +++ b/lib-python/2.7/lib2to3/tests/test_fixers.py @@ -2981,6 +2981,10 @@ self.unchanged(a) a = """sorted(filter(f, 'abc'), key=blah)[0]""" self.unchanged(a) + a = """enumerate(filter(f, 'abc'))""" + self.unchanged(a) + a = """enumerate(filter(f, 'abc'), start=1)""" + self.unchanged(a) a = """for i in filter(f, 'abc'): pass""" self.unchanged(a) a = """[x for x in filter(f, 'abc')]""" @@ -3089,6 +3093,10 @@ self.unchanged(a) a = """sorted(map(f, 'abc'), key=blah)[0]""" self.unchanged(a) + a = """enumerate(map(f, 'abc'))""" + self.unchanged(a) + a = """enumerate(map(f, 'abc'), start=1)""" + self.unchanged(a) a = """for i in map(f, 'abc'): pass""" self.unchanged(a) a = """[x for x in map(f, 'abc')]""" @@ -3152,6 +3160,10 @@ self.unchanged(a) a = """sorted(zip(a, b), key=blah)[0]""" self.unchanged(a) + a = """enumerate(zip(a, b))""" + self.unchanged(a) + a = """enumerate(zip(a, b), start=1)""" + self.unchanged(a) a = """for i in zip(a, b): pass""" self.unchanged(a) a = """[x for x in zip(a, b)]""" diff --git a/lib-python/2.7/locale.py b/lib-python/2.7/locale.py --- a/lib-python/2.7/locale.py +++ b/lib-python/2.7/locale.py @@ -18,6 +18,14 @@ import operator import functools +try: + _unicode = unicode +except NameError: + # If Python is built without Unicode support, the unicode type + # will not exist. Fake one. + class _unicode(object): + pass + # Try importing the _locale module. # # If this fails, fall back on a basic 'C' locale emulation. @@ -353,7 +361,7 @@ """ # Normalize the locale name and extract the encoding - if isinstance(localename, unicode): + if isinstance(localename, _unicode): localename = localename.encode('ascii') fullname = localename.translate(_ascii_lower_map) if ':' in fullname: diff --git a/lib-python/2.7/logging/__init__.py b/lib-python/2.7/logging/__init__.py --- a/lib-python/2.7/logging/__init__.py +++ b/lib-python/2.7/logging/__init__.py @@ -180,7 +180,7 @@ _releaseLock() def _checkLevel(level): - if isinstance(level, int): + if isinstance(level, (int, long)): rv = level elif str(level) == level: if level not in _levelNames: @@ -624,7 +624,8 @@ # This function can be called during module teardown, when globals are # set to None. If _acquireLock is None, assume this is the case and do # nothing. - if _acquireLock is not None: + if (_acquireLock is not None and _handlerList is not None and + _releaseLock is not None): _acquireLock() try: if wr in _handlerList: @@ -1173,11 +1174,12 @@ if self.isEnabledFor(ERROR): self._log(ERROR, msg, args, **kwargs) - def exception(self, msg, *args): + def exception(self, msg, *args, **kwargs): """ Convenience method for logging an ERROR with exception information. """ - self.error(msg, exc_info=1, *args) + kwargs['exc_info'] = 1 + self.error(msg, *args, **kwargs) def critical(self, msg, *args, **kwargs): """ @@ -1250,7 +1252,7 @@ all the handlers of this logger to handle the record. """ if _srcfile: - #IronPython doesn't track Python frames, so findCaller throws an + #IronPython doesn't track Python frames, so findCaller raises an #exception on some versions of IronPython. We trap it here so that #IronPython can use logging. try: @@ -1582,12 +1584,13 @@ basicConfig() root.error(msg, *args, **kwargs) -def exception(msg, *args): +def exception(msg, *args, **kwargs): """ Log a message with severity 'ERROR' on the root logger, with exception information. """ - error(msg, exc_info=1, *args) + kwargs['exc_info'] = 1 + error(msg, *args, **kwargs) def warning(msg, *args, **kwargs): """ diff --git a/lib-python/2.7/logging/handlers.py b/lib-python/2.7/logging/handlers.py --- a/lib-python/2.7/logging/handlers.py +++ b/lib-python/2.7/logging/handlers.py @@ -23,7 +23,7 @@ To use, simply 'import logging.handlers' and log away! """ -import logging, socket, os, cPickle, struct, time, re +import errno, logging, socket, os, cPickle, struct, time, re from stat import ST_DEV, ST_INO, ST_MTIME try: @@ -139,7 +139,6 @@ os.remove(dfn) os.rename(self.baseFilename, dfn) #print "%s -> %s" % (self.baseFilename, dfn) - self.mode = 'w' self.stream = self._open() def shouldRollover(self, record): @@ -354,7 +353,6 @@ for s in self.getFilesToDelete(): os.remove(s) #print "%s -> %s" % (self.baseFilename, dfn) - self.mode = 'w' self.stream = self._open() newRolloverAt = self.computeRollover(currentTime) while newRolloverAt <= currentTime: @@ -392,11 +390,13 @@ """ def __init__(self, filename, mode='a', encoding=None, delay=0): logging.FileHandler.__init__(self, filename, mode, encoding, delay) - if not os.path.exists(self.baseFilename): - self.dev, self.ino = -1, -1 - else: - stat = os.stat(self.baseFilename) - self.dev, self.ino = stat[ST_DEV], stat[ST_INO] + self.dev, self.ino = -1, -1 + self._statstream() + + def _statstream(self): + if self.stream: + sres = os.fstat(self.stream.fileno()) + self.dev, self.ino = sres[ST_DEV], sres[ST_INO] def emit(self, record): """ @@ -406,19 +406,27 @@ has, close the old stream and reopen the file to get the current stream. """ - if not os.path.exists(self.baseFilename): - stat = None - changed = 1 - else: - stat = os.stat(self.baseFilename) - changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino) - if changed and self.stream is not None: - self.stream.flush() - self.stream.close() - self.stream = self._open() - if stat is None: - stat = os.stat(self.baseFilename) - self.dev, self.ino = stat[ST_DEV], stat[ST_INO] + # Reduce the chance of race conditions by stat'ing by path only + # once and then fstat'ing our new fd if we opened a new log stream. + # See issue #14632: Thanks to John Mulligan for the problem report + # and patch. + try: + # stat the file by path, checking for existence + sres = os.stat(self.baseFilename) + except OSError as err: + if err.errno == errno.ENOENT: + sres = None + else: + raise + # compare file system stat with that of our stream file handle + if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino: + if self.stream is not None: + # we have an open file handle, clean it up + self.stream.flush() + self.stream.close() + # open a new file handle and get new stat info from that fd + self.stream = self._open() + self._statstream() logging.FileHandler.emit(self, record) class SocketHandler(logging.Handler): @@ -528,9 +536,16 @@ """ ei = record.exc_info if ei: - dummy = self.format(record) # just to get traceback text into record.exc_text + # just to get traceback text into record.exc_text ... + dummy = self.format(record) record.exc_info = None # to avoid Unpickleable error - s = cPickle.dumps(record.__dict__, 1) + # See issue #14436: If msg or args are objects, they may not be + # available on the receiving end. So we convert the msg % args + # to a string, save it as msg and zap the args. + d = dict(record.__dict__) + d['msg'] = record.getMessage() + d['args'] = None + s = cPickle.dumps(d, 1) if ei: record.exc_info = ei # for next handler slen = struct.pack(">L", len(s)) @@ -747,14 +762,12 @@ self.formatter = None def _connect_unixsocket(self, address): - self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) - # syslog may require either DGRAM or STREAM sockets + self.socket = socket.socket(socket.AF_UNIX, self.socktype) try: self.socket.connect(address) except socket.error: self.socket.close() - self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.socket.connect(address) + raise # curious: when talking to the unix-domain '/dev/log' socket, a # zero-terminator seems to be required. this string is placed @@ -814,8 +827,6 @@ # Message is a string. Convert to bytes as required by RFC 5424 if type(msg) is unicode: msg = msg.encode('utf-8') - if codecs: - msg = codecs.BOM_UTF8 + msg msg = prio + msg try: if self.unixsocket: @@ -868,6 +879,7 @@ self.toaddrs = toaddrs self.subject = subject self.secure = secure + self._timeout = 5.0 def getSubject(self, record): """ @@ -890,7 +902,7 @@ port = self.mailport if not port: port = smtplib.SMTP_PORT - smtp = smtplib.SMTP(self.mailhost, port) + smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout) msg = self.format(record) msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % ( self.fromaddr, diff --git a/lib-python/2.7/mailbox.py b/lib-python/2.7/mailbox.py --- a/lib-python/2.7/mailbox.py +++ b/lib-python/2.7/mailbox.py @@ -197,6 +197,9 @@ """Flush and close the mailbox.""" raise NotImplementedError('Method must be implemented by subclass') + # Whether each message must end in a newline + _append_newline = False + def _dump_message(self, message, target, mangle_from_=False): # Most files are opened in binary mode to allow predictable seeking. # To get native line endings on disk, the user-friendly \n line endings @@ -207,13 +210,21 @@ gen = email.generator.Generator(buffer, mangle_from_, 0) gen.flatten(message) buffer.seek(0) - target.write(buffer.read().replace('\n', os.linesep)) + data = buffer.read().replace('\n', os.linesep) + target.write(data) + if self._append_newline and not data.endswith(os.linesep): + # Make sure the message ends with a newline + target.write(os.linesep) elif isinstance(message, str): if mangle_from_: message = message.replace('\nFrom ', '\n>From ') message = message.replace('\n', os.linesep) target.write(message) + if self._append_newline and not message.endswith(os.linesep): + # Make sure the message ends with a newline + target.write(os.linesep) elif hasattr(message, 'read'): + lastline = None while True: line = message.readline() if line == '': @@ -222,6 +233,10 @@ line = '>From ' + line[5:] line = line.replace('\n', os.linesep) target.write(line) + lastline = line + if self._append_newline and lastline and not lastline.endswith(os.linesep): + # Make sure the message ends with a newline + target.write(os.linesep) else: raise TypeError('Invalid message type: %s' % type(message)) @@ -561,16 +576,19 @@ self._file = f self._toc = None self._next_key = 0 - self._pending = False # No changes require rewriting the file. + self._pending = False # No changes require rewriting the file. + self._pending_sync = False # No need to sync the file self._locked = False - self._file_length = None # Used to record mailbox size + self._file_length = None # Used to record mailbox size def add(self, message): """Add message and return assigned key.""" self._lookup() self._toc[self._next_key] = self._append_message(message) self._next_key += 1 - self._pending = True + # _append_message appends the message to the mailbox file. We + # don't need a full rewrite + rename, sync is enough. + self._pending_sync = True return self._next_key - 1 def remove(self, key): @@ -616,6 +634,11 @@ def flush(self): """Write any pending changes to disk.""" if not self._pending: + if self._pending_sync: + # Messages have only been added, so syncing the file + # is enough. + _sync_flush(self._file) + self._pending_sync = False return # In order to be writing anything out at all, self._toc must @@ -649,6 +672,7 @@ new_file.write(buffer) new_toc[key] = (new_start, new_file.tell()) self._post_message_hook(new_file) + self._file_length = new_file.tell() except: new_file.close() os.remove(new_file.name) @@ -656,6 +680,9 @@ _sync_close(new_file) # self._file is about to get replaced, so no need to sync. self._file.close() + # Make sure the new file's mode is the same as the old file's + mode = os.stat(self._path).st_mode + os.chmod(new_file.name, mode) try: os.rename(new_file.name, self._path) except OSError, e: @@ -668,6 +695,7 @@ self._file = open(self._path, 'rb+') self._toc = new_toc self._pending = False + self._pending_sync = False if self._locked: _lock_file(self._file, dotlock=False) @@ -704,6 +732,12 @@ """Append message to mailbox and return (start, stop) offsets.""" self._file.seek(0, 2) before = self._file.tell() + if len(self._toc) == 0 and not self._pending: + # This is the first message, and the _pre_mailbox_hook + # hasn't yet been called. If self._pending is True, + # messages have been removed, so _pre_mailbox_hook must + # have been called already. + self._pre_mailbox_hook(self._file) try: self._pre_message_hook(self._file) offsets = self._install_message(message) @@ -778,30 +812,48 @@ _mangle_from_ = True + # All messages must end in a newline character, and + # _post_message_hooks outputs an empty line between messages. + _append_newline = True + def __init__(self, path, factory=None, create=True): """Initialize an mbox mailbox.""" self._message_factory = mboxMessage _mboxMMDF.__init__(self, path, factory, create) - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - if f.tell() != 0: - f.write(os.linesep) + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(os.linesep) def _generate_toc(self): """Generate key-to-(start, stop) table of contents.""" starts, stops = [], [] + last_was_empty = False self._file.seek(0) while True: line_pos = self._file.tell() line = self._file.readline() if line.startswith('From '): if len(stops) < len(starts): + if last_was_empty: + stops.append(line_pos - len(os.linesep)) + else: + # The last line before the "From " line wasn't + # blank, but we consider it a start of a + # message anyway. + stops.append(line_pos) + starts.append(line_pos) + last_was_empty = False + elif not line: + if last_was_empty: stops.append(line_pos - len(os.linesep)) - starts.append(line_pos) - elif line == '': - stops.append(line_pos) + else: + stops.append(line_pos) break + elif line == os.linesep: + last_was_empty = True + else: + last_was_empty = False self._toc = dict(enumerate(zip(starts, stops))) self._next_key = len(self._toc) self._file_length = self._file.tell() @@ -1367,9 +1419,9 @@ line = message.readline() self._file.write(line.replace('\n', os.linesep)) if line == '\n' or line == '': - self._file.write('*** EOOH ***' + os.linesep) if first_pass: first_pass = False + self._file.write('*** EOOH ***' + os.linesep) message.seek(original_pos) else: break diff --git a/lib-python/2.7/mimetypes.py b/lib-python/2.7/mimetypes.py --- a/lib-python/2.7/mimetypes.py +++ b/lib-python/2.7/mimetypes.py @@ -432,11 +432,12 @@ '.hdf' : 'application/x-hdf', '.htm' : 'text/html', '.html' : 'text/html', + '.ico' : 'image/vnd.microsoft.icon', '.ief' : 'image/ief', '.jpe' : 'image/jpeg', '.jpeg' : 'image/jpeg', '.jpg' : 'image/jpeg', - '.js' : 'application/x-javascript', + '.js' : 'application/javascript', '.ksh' : 'text/plain', '.latex' : 'application/x-latex', '.m1v' : 'video/mpeg', diff --git a/lib-python/2.7/multiprocessing/connection.py b/lib-python/2.7/multiprocessing/connection.py --- a/lib-python/2.7/multiprocessing/connection.py +++ b/lib-python/2.7/multiprocessing/connection.py @@ -186,6 +186,8 @@ ''' if duplex: s1, s2 = socket.socketpair() + s1.setblocking(True) + s2.setblocking(True) c1 = _multiprocessing.Connection(os.dup(s1.fileno())) c2 = _multiprocessing.Connection(os.dup(s2.fileno())) s1.close() @@ -198,7 +200,6 @@ return c1, c2 else: - from _multiprocessing import win32 def Pipe(duplex=True): @@ -251,6 +252,7 @@ self._socket = socket.socket(getattr(socket, family)) try: self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._socket.setblocking(True) self._socket.bind(address) self._socket.listen(backlog) self._address = self._socket.getsockname() @@ -269,6 +271,7 @@ def accept(self): s, self._last_accepted = self._socket.accept() + s.setblocking(True) fd = duplicate(s.fileno()) conn = _multiprocessing.Connection(fd) s.close() @@ -286,6 +289,7 @@ ''' family = address_type(address) s = socket.socket( getattr(socket, family) ) + s.setblocking(True) t = _init_timeout() while 1: @@ -348,7 +352,10 @@ try: win32.ConnectNamedPipe(handle, win32.NULL) except WindowsError, e: - if e.args[0] != win32.ERROR_PIPE_CONNECTED: + # ERROR_NO_DATA can occur if a client has already connected, + # written data and then disconnected -- see Issue 14725. + if e.args[0] not in (win32.ERROR_PIPE_CONNECTED, + win32.ERROR_NO_DATA): raise return _multiprocessing.PipeConnection(handle) diff --git a/lib-python/2.7/multiprocessing/dummy/__init__.py b/lib-python/2.7/multiprocessing/dummy/__init__.py --- a/lib-python/2.7/multiprocessing/dummy/__init__.py +++ b/lib-python/2.7/multiprocessing/dummy/__init__.py @@ -70,7 +70,8 @@ def start(self): assert self._parent is current_process() self._start_called = True - self._parent._children[self] = None + if hasattr(self._parent, '_children'): + self._parent._children[self] = None threading.Thread.start(self) @property diff --git a/lib-python/2.7/multiprocessing/forking.py b/lib-python/2.7/multiprocessing/forking.py --- a/lib-python/2.7/multiprocessing/forking.py +++ b/lib-python/2.7/multiprocessing/forking.py @@ -35,6 +35,7 @@ import os import sys import signal +import errno from multiprocessing import util, process @@ -129,12 +130,17 @@ def poll(self, flag=os.WNOHANG): if self.returncode is None: - try: - pid, sts = os.waitpid(self.pid, flag) - except os.error: - # Child process not yet created. See #1731717 - # e.errno == errno.ECHILD == 10 - return None + while True: + try: + pid, sts = os.waitpid(self.pid, flag) + except os.error as e: + if e.errno == errno.EINTR: + continue + # Child process not yet created. See #1731717 + # e.errno == errno.ECHILD == 10 + return None + else: + break if pid == self.pid: if os.WIFSIGNALED(sts): self.returncode = -os.WTERMSIG(sts) @@ -336,7 +342,7 @@ ''' Returns prefix of command line used for spawning a child process ''' - if process.current_process()._identity==() and is_forking(sys.argv): + if getattr(process.current_process(), '_inheriting', False): raise RuntimeError(''' Attempt to start a new process before the current process has finished its bootstrapping phase. diff --git a/lib-python/2.7/multiprocessing/pool.py b/lib-python/2.7/multiprocessing/pool.py --- a/lib-python/2.7/multiprocessing/pool.py +++ b/lib-python/2.7/multiprocessing/pool.py @@ -68,6 +68,23 @@ # Code run by worker processes # +class MaybeEncodingError(Exception): + """Wraps possible unpickleable errors, so they can be + safely sent through the socket.""" + + def __init__(self, exc, value): + self.exc = repr(exc) + self.value = repr(value) + super(MaybeEncodingError, self).__init__(self.exc, self.value) + + def __str__(self): + return "Error sending result: '%s'. Reason: '%s'" % (self.value, + self.exc) + + def __repr__(self): + return "" % str(self) + + def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None): assert maxtasks is None or (type(maxtasks) == int and maxtasks > 0) put = outqueue.put @@ -96,7 +113,13 @@ result = (True, func(*args, **kwds)) except Exception, e: result = (False, e) - put((job, i, result)) + try: + put((job, i, result)) + except Exception as e: + wrapped = MaybeEncodingError(e, result[1]) + debug("Possible encoding error while sending result: %s" % ( + wrapped)) + put((job, i, (False, wrapped))) completed += 1 debug('worker exiting after %d tasks' % completed) @@ -466,7 +489,8 @@ # We must wait for the worker handler to exit before terminating # workers because we don't want workers to be restarted behind our back. debug('joining worker handler') - worker_handler.join() + if threading.current_thread() is not worker_handler: + worker_handler.join(1e100) # Terminate workers which haven't already finished. if pool and hasattr(pool[0], 'terminate'): @@ -476,10 +500,12 @@ p.terminate() debug('joining task handler') - task_handler.join(1e100) + if threading.current_thread() is not task_handler: + task_handler.join(1e100) debug('joining result handler') - result_handler.join(1e100) + if threading.current_thread() is not result_handler: + result_handler.join(1e100) if pool and hasattr(pool[0], 'terminate'): debug('joining pool workers') @@ -553,6 +579,7 @@ if chunksize <= 0: self._number_left = 0 self._ready = True + del cache[self._job] else: self._number_left = length//chunksize + bool(length % chunksize) diff --git a/lib-python/2.7/multiprocessing/process.py b/lib-python/2.7/multiprocessing/process.py --- a/lib-python/2.7/multiprocessing/process.py +++ b/lib-python/2.7/multiprocessing/process.py @@ -262,12 +262,12 @@ except SystemExit, e: if not e.args: exitcode = 1 - elif type(e.args[0]) is int: + elif isinstance(e.args[0], int): exitcode = e.args[0] else: - sys.stderr.write(e.args[0] + '\n') + sys.stderr.write(str(e.args[0]) + '\n') sys.stderr.flush() - exitcode = 1 + exitcode = 0 if isinstance(e.args[0], str) else 1 except: exitcode = 1 import traceback diff --git a/lib-python/2.7/multiprocessing/util.py b/lib-python/2.7/multiprocessing/util.py --- a/lib-python/2.7/multiprocessing/util.py +++ b/lib-python/2.7/multiprocessing/util.py @@ -247,6 +247,12 @@ Finalizers with highest priority are called first; finalizers with the same priority will be called in reverse order of creation. ''' + if _finalizer_registry is None: + # This function may be called after this module's globals are + # destroyed. See the _exit_function function in this module for more + # notes. + return + if minpriority is None: f = lambda p : p[0][0] is not None else: @@ -278,21 +284,38 @@ _exiting = False -def _exit_function(): +def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers, + active_children=active_children, + current_process=current_process): + # NB: we hold on to references to functions in the arglist due to the + # situation described below, where this function is called after this + # module's globals are destroyed. + global _exiting info('process shutting down') debug('running all "atexit" finalizers with priority >= 0') _run_finalizers(0) - for p in active_children(): - if p._daemonic: - info('calling terminate() for daemon %s', p.name) - p._popen.terminate() + if current_process() is not None: + # NB: we check if the current process is None here because if + # it's None, any call to ``active_children()`` will throw an + # AttributeError (active_children winds up trying to get + # attributes from util._current_process). This happens in a + # variety of shutdown circumstances that are not well-understood + # because module-scope variables are not apparently supposed to + # be destroyed until after this function is called. However, + # they are indeed destroyed before this function is called. See + # issues 9775 and 15881. Also related: 4106, 9205, and 9207. - for p in active_children(): - info('calling join() for process %s', p.name) - p.join() + for p in active_children(): + if p._daemonic: + info('calling terminate() for daemon %s', p.name) + p._popen.terminate() + + for p in active_children(): + info('calling join() for process %s', p.name) + p.join() debug('running the remaining "atexit" finalizers') _run_finalizers() diff --git a/lib-python/2.7/plat-generic/regen b/lib-python/2.7/plat-generic/regen --- a/lib-python/2.7/plat-generic/regen +++ b/lib-python/2.7/plat-generic/regen @@ -1,3 +1,3 @@ #! /bin/sh set -v -python$EXE ../../Tools/scripts/h2py.py -i '(u_long)' /usr/include/netinet/in.h +eval $PYTHON_FOR_BUILD ../../Tools/scripts/h2py.py -i "'(u_long)'" /usr/include/netinet/in.h diff --git a/lib-python/2.7/platform.py b/lib-python/2.7/platform.py --- a/lib-python/2.7/platform.py +++ b/lib-python/2.7/platform.py @@ -673,8 +673,13 @@ release = '7' else: release = '2008ServerR2' + elif min == 2: + if product_type == VER_NT_WORKSTATION: + release = '8' + else: + release = '2012Server' else: - release = 'post2008Server' + release = 'post2012Server' else: if not release: @@ -1020,16 +1025,38 @@ case the command should fail. """ + + # We do the import here to avoid a bootstrap issue. + # See c73b90b6dadd changeset. + # + # [..] + # ranlib libpython2.7.a + # gcc -o python \ + # Modules/python.o \ + # libpython2.7.a -lsocket -lnsl -ldl -lm + # Traceback (most recent call last): + # File "./setup.py", line 8, in + # from platform import machine as platform_machine + # File "[..]/build/Lib/platform.py", line 116, in + # import sys,string,os,re,subprocess + # File "[..]/build/Lib/subprocess.py", line 429, in + # import select + # ImportError: No module named select + + import subprocess + if sys.platform in ('dos','win32','win16','os2'): # XXX Others too ? return default - target = _follow_symlinks(target).replace('"', '\\"') + target = _follow_symlinks(target) try: - f = os.popen('file "%s" 2> %s' % (target, DEV_NULL)) + proc = subprocess.Popen(['file', target], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except (AttributeError,os.error): return default - output = string.strip(f.read()) - rc = f.close() + output = proc.communicate()[0] + rc = proc.wait() if not output or rc: return default else: diff --git a/lib-python/2.7/posixpath.py b/lib-python/2.7/posixpath.py --- a/lib-python/2.7/posixpath.py +++ b/lib-python/2.7/posixpath.py @@ -17,6 +17,14 @@ import warnings from genericpath import * +try: + _unicode = unicode +except NameError: + # If Python is built without Unicode support, the unicode type + # will not exist. Fake one. + class _unicode(object): + pass + __all__ = ["normcase","isabs","join","splitdrive","split","splitext", "basename","dirname","commonprefix","getsize","getmtime", "getatime","getctime","islink","exists","lexists","isdir","isfile", @@ -60,7 +68,8 @@ def join(a, *p): """Join two or more pathname components, inserting '/' as needed. If any component is an absolute path, all previous path components - will be discarded.""" + will be discarded. An empty last part will result in a path that + ends with a separator.""" path = a for b in p: if b.startswith('/'): @@ -267,8 +276,8 @@ except KeyError: return path userhome = pwent.pw_dir - userhome = userhome.rstrip('/') or userhome - return userhome + path[i:] + userhome = userhome.rstrip('/') + return (userhome + path[i:]) or '/' # Expand paths containing shell variable substitutions. @@ -312,7 +321,7 @@ def normpath(path): """Normalize path, eliminating double slashes, etc.""" # Preserve unicode (if path is unicode) - slash, dot = (u'/', u'.') if isinstance(path, unicode) else ('/', '.') + slash, dot = (u'/', u'.') if isinstance(path, _unicode) else ('/', '.') if path == '': return dot initial_slashes = path.startswith('/') @@ -341,7 +350,7 @@ def abspath(path): """Return an absolute path.""" if not isabs(path): - if isinstance(path, unicode): + if isinstance(path, _unicode): cwd = os.getcwdu() else: cwd = os.getcwd() @@ -355,46 +364,53 @@ def realpath(filename): """Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path.""" - if isabs(filename): - bits = ['/'] + filename.split('/')[1:] - else: - bits = [''] + filename.split('/') + path, ok = _joinrealpath('', filename, {}) + return abspath(path) - for i in range(2, len(bits)+1): - component = join(*bits[0:i]) - # Resolve symbolic links. - if islink(component): - resolved = _resolve_link(component) - if resolved is None: - # Infinite loop -- return original component + rest of the path - return abspath(join(*([component] + bits[i:]))) +# Join two paths, normalizing ang eliminating any symbolic links +# encountered in the second path. +def _joinrealpath(path, rest, seen): + if isabs(rest): + rest = rest[1:] + path = sep + + while rest: + name, _, rest = rest.partition(sep) + if not name or name == curdir: + # current dir + continue + if name == pardir: + # parent dir + if path: + path, name = split(path) + if name == pardir: + path = join(path, pardir, pardir) else: - newpath = join(*([resolved] + bits[i:])) - return realpath(newpath) + path = pardir + continue + newpath = join(path, name) + if not islink(newpath): + path = newpath + continue + # Resolve the symbolic link + if newpath in seen: + # Already seen this path + path = seen[newpath] + if path is not None: + # use cached value + continue + # The symlink is not resolved, so we must have a symlink loop. + # Return already resolved part + rest of the path unchanged. + return join(newpath, rest), False + seen[newpath] = None # not resolved symlink + path, ok = _joinrealpath(path, os.readlink(newpath), seen) + if not ok: + return join(path, rest), False + seen[newpath] = path # resolved symlink - return abspath(filename) + return path, True -def _resolve_link(path): - """Internal helper function. Takes a path and follows symlinks - until we either arrive at something that isn't a symlink, or - encounter a path we've seen before (meaning that there's a loop). - """ - paths_seen = set() - while islink(path): - if path in paths_seen: - # Already seen this path, so we must have a symlink loop - return None - paths_seen.add(path) - # Resolve where the link points to - resolved = os.readlink(path) - if not isabs(resolved): - dir = dirname(path) - path = normpath(join(dir, resolved)) - else: - path = normpath(resolved) - return path - supports_unicode_filenames = (sys.platform == 'darwin') def relpath(path, start=curdir): diff --git a/lib-python/2.7/pstats.py b/lib-python/2.7/pstats.py --- a/lib-python/2.7/pstats.py +++ b/lib-python/2.7/pstats.py @@ -120,8 +120,8 @@ self.stats = arg.stats arg.stats = {} if not self.stats: - raise TypeError, "Cannot create or construct a %r object from '%r''" % ( - self.__class__, arg) + raise TypeError("Cannot create or construct a %r object from %r" + % (self.__class__, arg)) return def get_top_level_stats(self): @@ -172,15 +172,19 @@ # along with some printable description sort_arg_dict_default = { "calls" : (((1,-1), ), "call count"), + "ncalls" : (((1,-1), ), "call count"), + "cumtime" : (((3,-1), ), "cumulative time"), "cumulative": (((3,-1), ), "cumulative time"), "file" : (((4, 1), ), "file name"), + "filename" : (((4, 1), ), "file name"), "line" : (((5, 1), ), "line number"), "module" : (((4, 1), ), "file name"), "name" : (((6, 1), ), "function name"), "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"), - "pcalls" : (((0,-1), ), "call count"), + "pcalls" : (((0,-1), ), "primitive call count"), "stdname" : (((7, 1), ), "standard name"), "time" : (((2,-1), ), "internal time"), + "tottime" : (((2,-1), ), "internal time"), } def get_sort_arg_defs(self): diff --git a/lib-python/2.7/py_compile.py b/lib-python/2.7/py_compile.py --- a/lib-python/2.7/py_compile.py +++ b/lib-python/2.7/py_compile.py @@ -112,7 +112,7 @@ try: codeobject = __builtin__.compile(codestring, dfile or file,'exec') except Exception,err: - py_exc = PyCompileError(err.__class__,err.args,dfile or file) + py_exc = PyCompileError(err.__class__, err, dfile or file) if doraise: raise py_exc else: diff --git a/lib-python/2.7/pyclbr.py b/lib-python/2.7/pyclbr.py --- a/lib-python/2.7/pyclbr.py +++ b/lib-python/2.7/pyclbr.py @@ -128,6 +128,8 @@ parent = _readmodule(package, path, inpackage) if inpackage is not None: package = "%s.%s" % (inpackage, package) + if not '__path__' in parent: + raise ImportError('No package named {}'.format(package)) return _readmodule(submodule, parent['__path__'], package) # Search the path for the module diff --git a/lib-python/2.7/pydoc.py b/lib-python/2.7/pydoc.py --- a/lib-python/2.7/pydoc.py +++ b/lib-python/2.7/pydoc.py @@ -1498,7 +1498,8 @@ raise ImportError, 'no Python documentation found for %r' % thing return object, thing else: - return thing, getattr(thing, '__name__', None) + name = getattr(thing, '__name__', None) + return thing, name if isinstance(name, str) else None def render_doc(thing, title='Python Library Documentation: %s', forceload=0): """Render text documentation, given an object or a path to an object.""" @@ -1799,7 +1800,7 @@ Welcome to Python %s! This is the online help utility. If this is your first time using Python, you should definitely check out -the tutorial on the Internet at http://docs.python.org/tutorial/. +the tutorial on the Internet at http://docs.python.org/%s/tutorial/. Enter the name of any module, keyword, or topic to get help on writing Python programs and using Python modules. To quit this help utility and @@ -1809,7 +1810,7 @@ "keywords", or "topics". Each module also comes with a one-line summary of what it does; to list the modules whose summaries contain a given word such as "spam", type "modules spam". -''' % sys.version[:3]) +''' % tuple([sys.version[:3]]*2)) def list(self, items, columns=4, width=80): items = items[:] diff --git a/lib-python/2.7/random.py b/lib-python/2.7/random.py --- a/lib-python/2.7/random.py +++ b/lib-python/2.7/random.py @@ -457,27 +457,25 @@ if kappa <= 1e-6: return TWOPI * random() - a = 1.0 + _sqrt(1.0 + 4.0 * kappa * kappa) - b = (a - _sqrt(2.0 * a))/(2.0 * kappa) - r = (1.0 + b * b)/(2.0 * b) + s = 0.5 / kappa + r = s + _sqrt(1.0 + s * s) while 1: u1 = random() + z = _cos(_pi * u1) - z = _cos(_pi * u1) - f = (1.0 + r * z)/(r + z) - c = kappa * (r - f) - + d = z / (r + z) u2 = random() - - if u2 < c * (2.0 - c) or u2 <= c * _exp(1.0 - c): + if u2 < 1.0 - d * d or u2 <= (1.0 - d) * _exp(d): break + q = 1.0 / r + f = (q + z) / (1.0 + q * z) u3 = random() if u3 > 0.5: - theta = (mu % TWOPI) + _acos(f) + theta = (mu + _acos(f)) % TWOPI else: - theta = (mu % TWOPI) - _acos(f) + theta = (mu - _acos(f)) % TWOPI return theta diff --git a/lib-python/2.7/rfc822.py b/lib-python/2.7/rfc822.py --- a/lib-python/2.7/rfc822.py +++ b/lib-python/2.7/rfc822.py @@ -212,7 +212,7 @@ You may override this method if your application wants to bend the rules, e.g. to strip trailing whitespace, or to recognize MH template separators ('--------'). For convenience (e.g. for code reading from - sockets) a line consisting of \r\n also matches. + sockets) a line consisting of \\r\\n also matches. """ return line in _blanklines diff --git a/lib-python/2.7/rlcompleter.py b/lib-python/2.7/rlcompleter.py --- a/lib-python/2.7/rlcompleter.py +++ b/lib-python/2.7/rlcompleter.py @@ -1,13 +1,11 @@ -"""Word completion for GNU readline 2.0. +"""Word completion for GNU readline. -This requires the latest extension to the readline module. The completer -completes keywords, built-ins and globals in a selectable namespace (which -defaults to __main__); when completing NAME.NAME..., it evaluates (!) the -expression up to the last dot and completes its attributes. +The completer completes keywords, built-ins and globals in a selectable +namespace (which defaults to __main__); when completing NAME.NAME..., it +evaluates (!) the expression up to the last dot and completes its attributes. -It's very cool to do "import sys" type "sys.", hit the -completion key (twice), and see the list of names defined by the -sys module! +It's very cool to do "import sys" type "sys.", hit the completion key (twice), +and see the list of names defined by the sys module! Tip: to use the tab key as the completion key, call @@ -15,18 +13,16 @@ Notes: -- Exceptions raised by the completer function are *ignored* (and -generally cause the completion to fail). This is a feature -- since -readline sets the tty device in raw (or cbreak) mode, printing a -traceback wouldn't work well without some complicated hoopla to save, -reset and restore the tty state. +- Exceptions raised by the completer function are *ignored* (and generally cause + the completion to fail). This is a feature -- since readline sets the tty + device in raw (or cbreak) mode, printing a traceback wouldn't work well + without some complicated hoopla to save, reset and restore the tty state. -- The evaluation of the NAME.NAME... form may cause arbitrary -application defined code to be executed if an object with a -__getattr__ hook is found. Since it is the responsibility of the -application (or the user) to enable this feature, I consider this an -acceptable risk. More complicated expressions (e.g. function calls or -indexing operations) are *not* evaluated. +- The evaluation of the NAME.NAME... form may cause arbitrary application + defined code to be executed if an object with a __getattr__ hook is found. + Since it is the responsibility of the application (or the user) to enable this + feature, I consider this an acceptable risk. More complicated expressions + (e.g. function calls or indexing operations) are *not* evaluated. - GNU readline is also used by the built-in functions input() and raw_input(), and thus these also benefit/suffer from the completer @@ -35,7 +31,7 @@ its input. - When the original stdin is not a tty device, GNU readline is never -used, and this module (and the readline module) are silently inactive. + used, and this module (and the readline module) are silently inactive. """ diff --git a/lib-python/2.7/runpy.py b/lib-python/2.7/runpy.py --- a/lib-python/2.7/runpy.py +++ b/lib-python/2.7/runpy.py @@ -200,7 +200,7 @@ pass else: # The following check looks a bit odd. The trick is that - # NullImporter throws ImportError if the supplied path is a + # NullImporter raises ImportError if the supplied path is a # *valid* directory entry (and hence able to be handled # by the standard import machinery) try: diff --git a/lib-python/2.7/shutil.py b/lib-python/2.7/shutil.py --- a/lib-python/2.7/shutil.py +++ b/lib-python/2.7/shutil.py @@ -102,8 +102,10 @@ try: os.chflags(dst, st.st_flags) except OSError, why: - if (not hasattr(errno, 'EOPNOTSUPP') or - why.errno != errno.EOPNOTSUPP): + for err in 'EOPNOTSUPP', 'ENOTSUP': + if hasattr(errno, err) and why.errno == getattr(errno, err): + break + else: raise def copy(src, dst): @@ -201,7 +203,7 @@ # Copying file access times may fail on Windows pass else: - errors.extend((src, dst, str(why))) + errors.append((src, dst, str(why))) if errors: raise Error, errors diff --git a/lib-python/2.7/smtplib.py b/lib-python/2.7/smtplib.py --- a/lib-python/2.7/smtplib.py +++ b/lib-python/2.7/smtplib.py @@ -818,13 +818,13 @@ try: self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(host) - except socket.error, msg: + except socket.error: if self.debuglevel > 0: print>>stderr, 'connect fail:', host if self.sock: self.sock.close() self.sock = None - raise socket.error, msg + raise (code, msg) = self.getreply() if self.debuglevel > 0: print>>stderr, "connect:", msg diff --git a/lib-python/2.7/socket.py b/lib-python/2.7/socket.py --- a/lib-python/2.7/socket.py +++ b/lib-python/2.7/socket.py @@ -319,8 +319,8 @@ self._wbuf.append(data) self._wbuf_len += len(data) if (self._wbufsize == 0 or - self._wbufsize == 1 and '\n' in data or - self._wbuf_len >= self._wbufsize): + (self._wbufsize == 1 and '\n' in data) or + (self._wbufsize > 1 and self._wbuf_len >= self._wbufsize)): self.flush() def writelines(self, list): diff --git a/lib-python/2.7/sqlite3/dbapi2.py b/lib-python/2.7/sqlite3/dbapi2.py --- a/lib-python/2.7/sqlite3/dbapi2.py +++ b/lib-python/2.7/sqlite3/dbapi2.py @@ -1,4 +1,4 @@ -#-*- coding: ISO-8859-1 -*- +# -*- coding: iso-8859-1 -*- # pysqlite2/dbapi2.py: the DB-API 2.0 interface # # Copyright (C) 2004-2005 Gerhard H?ring @@ -68,7 +68,7 @@ timepart_full = timepart.split(".") hours, minutes, seconds = map(int, timepart_full[0].split(":")) if len(timepart_full) == 2: - microseconds = int(timepart_full[1]) + microseconds = int('{:0<6.6}'.format(timepart_full[1].decode())) else: microseconds = 0 diff --git a/lib-python/2.7/sqlite3/dump.py b/lib-python/2.7/sqlite3/dump.py --- a/lib-python/2.7/sqlite3/dump.py +++ b/lib-python/2.7/sqlite3/dump.py @@ -25,9 +25,10 @@ FROM "sqlite_master" WHERE "sql" NOT NULL AND "type" == 'table' + ORDER BY "name" """ schema_res = cu.execute(q) - for table_name, type, sql in sorted(schema_res.fetchall()): + for table_name, type, sql in schema_res.fetchall(): if table_name == 'sqlite_sequence': yield('DELETE FROM "sqlite_sequence";') elif table_name == 'sqlite_stat1': @@ -42,7 +43,7 @@ # qtable, # sql.replace("''"))) else: - yield('{0};'.format(sql)) + yield('%s;' % sql) # Build the insert statement for each row of the current table table_name_ident = table_name.replace('"', '""') @@ -53,7 +54,7 @@ ",".join("""'||quote("{0}")||'""".format(col.replace('"', '""')) for col in column_names)) query_res = cu.execute(q) for row in query_res: - yield("{0};".format(row[0])) + yield("%s;" % row[0]) # Now when the type is 'index', 'trigger', or 'view' q = """ @@ -64,6 +65,6 @@ """ schema_res = cu.execute(q) for name, type, sql in schema_res.fetchall(): - yield('{0};'.format(sql)) + yield('%s;' % sql) yield('COMMIT;') diff --git a/lib-python/2.7/sqlite3/test/dump.py b/lib-python/2.7/sqlite3/test/dump.py --- a/lib-python/2.7/sqlite3/test/dump.py +++ b/lib-python/2.7/sqlite3/test/dump.py @@ -29,6 +29,8 @@ , "INSERT INTO \"t1\" VALUES(2,'foo2',30,30);" , + u"INSERT INTO \"t1\" VALUES(3,'f\xc3\xb6',40,10);" + , "CREATE TABLE t2(id integer, t2_i1 integer, " \ "t2_i2 integer, primary key (id)," \ "foreign key(t2_i1) references t1(t1_i1));" @@ -49,6 +51,27 @@ [self.assertEqual(expected_sqls[i], actual_sqls[i]) for i in xrange(len(expected_sqls))] + def CheckUnorderableRow(self): + # iterdump() should be able to cope with unorderable row types (issue #15545) + class UnorderableRow: + def __init__(self, cursor, row): + self.row = row + def __getitem__(self, index): + return self.row[index] + self.cx.row_factory = UnorderableRow + CREATE_ALPHA = """CREATE TABLE "alpha" ("one");""" + CREATE_BETA = """CREATE TABLE "beta" ("two");""" + expected = [ + "BEGIN TRANSACTION;", + CREATE_ALPHA, + CREATE_BETA, + "COMMIT;" + ] + self.cu.execute(CREATE_BETA) + self.cu.execute(CREATE_ALPHA) + got = list(self.cx.iterdump()) + self.assertEqual(expected, got) + def suite(): return unittest.TestSuite(unittest.makeSuite(DumpTests, "Check")) diff --git a/lib-python/2.7/sqlite3/test/hooks.py b/lib-python/2.7/sqlite3/test/hooks.py --- a/lib-python/2.7/sqlite3/test/hooks.py +++ b/lib-python/2.7/sqlite3/test/hooks.py @@ -76,6 +76,25 @@ except sqlite.OperationalError, e: self.assertEqual(e.args[0].lower(), "no such collation sequence: mycoll") + def CheckCollationReturnsLargeInteger(self): + def mycoll(x, y): + # reverse order + return -((x > y) - (x < y)) * 2**32 + con = sqlite.connect(":memory:") + con.create_collation("mycoll", mycoll) + sql = """ + select x from ( + select 'a' as x + union + select 'b' as x + union + select 'c' as x + ) order by x collate mycoll + """ + result = con.execute(sql).fetchall() + self.assertEqual(result, [('c',), ('b',), ('a',)], + msg="the expected order was not returned") + def CheckCollationRegisterTwice(self): """ Register two different collation functions under the same name. diff --git a/lib-python/2.7/sqlite3/test/regression.py b/lib-python/2.7/sqlite3/test/regression.py --- a/lib-python/2.7/sqlite3/test/regression.py +++ b/lib-python/2.7/sqlite3/test/regression.py @@ -1,4 +1,4 @@ -#-*- coding: ISO-8859-1 -*- +#-*- coding: iso-8859-1 -*- # pysqlite2/test/regression.py: pysqlite regression tests # # Copyright (C) 2006-2007 Gerhard H?ring @@ -285,6 +285,32 @@ cur.executemany("insert into b (baz) values (?)", ((i,) for i in foo())) + def CheckConvertTimestampMicrosecondPadding(self): + """ + http://bugs.python.org/issue14720 + + The microsecond parsing of convert_timestamp() should pad with zeros, + since the microsecond string "456" actually represents "456000". + """ + + con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES) + cur = con.cursor() + cur.execute("CREATE TABLE t (x TIMESTAMP)") + + # Microseconds should be 456000 + cur.execute("INSERT INTO t (x) VALUES ('2012-04-04 15:06:00.456')") + + # Microseconds should be truncated to 123456 + cur.execute("INSERT INTO t (x) VALUES ('2012-04-04 15:06:00.123456789')") + + cur.execute("SELECT * FROM t") + values = [x[0] for x in cur.fetchall()] + + self.assertEqual(values, [ + datetime.datetime(2012, 4, 4, 15, 6, 0, 456000), + datetime.datetime(2012, 4, 4, 15, 6, 0, 123456), + ]) + def suite(): regression_suite = unittest.makeSuite(RegressionTests, "Check") diff --git a/lib-python/2.7/sqlite3/test/userfunctions.py b/lib-python/2.7/sqlite3/test/userfunctions.py --- a/lib-python/2.7/sqlite3/test/userfunctions.py +++ b/lib-python/2.7/sqlite3/test/userfunctions.py @@ -374,14 +374,15 @@ val = cur.fetchone()[0] self.assertEqual(val, 60) -def authorizer_cb(action, arg1, arg2, dbname, source): - if action != sqlite.SQLITE_SELECT: - return sqlite.SQLITE_DENY - if arg2 == 'c2' or arg1 == 't2': - return sqlite.SQLITE_DENY - return sqlite.SQLITE_OK +class AuthorizerTests(unittest.TestCase): + @staticmethod + def authorizer_cb(action, arg1, arg2, dbname, source): + if action != sqlite.SQLITE_SELECT: + return sqlite.SQLITE_DENY + if arg2 == 'c2' or arg1 == 't2': + return sqlite.SQLITE_DENY + return sqlite.SQLITE_OK -class AuthorizerTests(unittest.TestCase): def setUp(self): self.con = sqlite.connect(":memory:") self.con.executescript(""" @@ -394,12 +395,12 @@ # For our security test: self.con.execute("select c2 from t2") - self.con.set_authorizer(authorizer_cb) + self.con.set_authorizer(self.authorizer_cb) def tearDown(self): pass - def CheckTableAccess(self): + def test_table_access(self): try: self.con.execute("select * from t2") except sqlite.DatabaseError, e: @@ -408,7 +409,7 @@ return self.fail("should have raised an exception due to missing privileges") - def CheckColumnAccess(self): + def test_column_access(self): try: self.con.execute("select c2 from t1") except sqlite.DatabaseError, e: @@ -417,11 +418,46 @@ return self.fail("should have raised an exception due to missing privileges") +class AuthorizerRaiseExceptionTests(AuthorizerTests): + @staticmethod + def authorizer_cb(action, arg1, arg2, dbname, source): + if action != sqlite.SQLITE_SELECT: + raise ValueError + if arg2 == 'c2' or arg1 == 't2': + raise ValueError + return sqlite.SQLITE_OK + +class AuthorizerIllegalTypeTests(AuthorizerTests): + @staticmethod + def authorizer_cb(action, arg1, arg2, dbname, source): + if action != sqlite.SQLITE_SELECT: + return 0.0 + if arg2 == 'c2' or arg1 == 't2': + return 0.0 + return sqlite.SQLITE_OK + +class AuthorizerLargeIntegerTests(AuthorizerTests): + @staticmethod + def authorizer_cb(action, arg1, arg2, dbname, source): + if action != sqlite.SQLITE_SELECT: + return 2**32 + if arg2 == 'c2' or arg1 == 't2': + return 2**32 + return sqlite.SQLITE_OK + + def suite(): function_suite = unittest.makeSuite(FunctionTests, "Check") aggregate_suite = unittest.makeSuite(AggregateTests, "Check") - authorizer_suite = unittest.makeSuite(AuthorizerTests, "Check") - return unittest.TestSuite((function_suite, aggregate_suite, authorizer_suite)) + authorizer_suite = unittest.makeSuite(AuthorizerTests) + return unittest.TestSuite(( + function_suite, + aggregate_suite, + authorizer_suite, + unittest.makeSuite(AuthorizerRaiseExceptionTests), + unittest.makeSuite(AuthorizerIllegalTypeTests), + unittest.makeSuite(AuthorizerLargeIntegerTests), + )) def test(): runner = unittest.TextTestRunner() diff --git a/lib-python/2.7/sre_compile.py b/lib-python/2.7/sre_compile.py --- a/lib-python/2.7/sre_compile.py +++ b/lib-python/2.7/sre_compile.py @@ -13,6 +13,7 @@ import _sre, sys import sre_parse from sre_constants import * +from _sre import MAXREPEAT assert _sre.MAGIC == MAGIC, "SRE module mismatch" diff --git a/lib-python/2.7/sre_constants.py b/lib-python/2.7/sre_constants.py --- a/lib-python/2.7/sre_constants.py +++ b/lib-python/2.7/sre_constants.py @@ -15,9 +15,7 @@ MAGIC = 20031017 -# max code word in this release - -MAXREPEAT = 65535 +from _sre import MAXREPEAT # SRE standard exception (access as sre.error) # should this really be here? diff --git a/lib-python/2.7/sre_parse.py b/lib-python/2.7/sre_parse.py --- a/lib-python/2.7/sre_parse.py +++ b/lib-python/2.7/sre_parse.py @@ -15,6 +15,7 @@ import sys from sre_constants import * +from _sre import MAXREPEAT SPECIAL_CHARS = ".\\[{()*+?^$|" REPEAT_CHARS = "*+?{" @@ -228,7 +229,7 @@ if code: return code code = CATEGORIES.get(escape) - if code: + if code and code[0] == IN: return code try: c = escape[1:2] @@ -498,10 +499,14 @@ continue if lo: min = int(lo) + if min >= MAXREPEAT: + raise OverflowError("the repetition number is too large") if hi: max = int(hi) - if max < min: - raise error, "bad repeat interval" + if max >= MAXREPEAT: + raise OverflowError("the repetition number is too large") + if max < min: + raise error("bad repeat interval") else: raise error, "not supported" # figure out which item to repeat @@ -541,6 +546,8 @@ break name = name + char group = 1 + if not name: + raise error("missing group name") if not isname(name): raise error, "bad character in group name" elif sourcematch("="): @@ -553,6 +560,8 @@ if char == ")": break name = name + char + if not name: + raise error("missing group name") if not isname(name): raise error, "bad character in group name" gid = state.groupdict.get(name) @@ -605,6 +614,8 @@ break condname = condname + char group = 2 + if not condname: + raise error("missing group name") if isname(condname): condgroup = state.groupdict.get(condname) if condgroup is None: @@ -723,7 +734,7 @@ break name = name + char if not name: - raise error, "bad group name" + raise error, "missing group name" try: index = int(name) if index < 0: diff --git a/lib-python/2.7/ssl.py b/lib-python/2.7/ssl.py --- a/lib-python/2.7/ssl.py +++ b/lib-python/2.7/ssl.py @@ -313,17 +313,19 @@ self.cert_reqs, self.ssl_version, self.ca_certs, self.ciphers) try: - socket.connect(self, addr) - if self.do_handshake_on_connect: - self.do_handshake() - except socket_error as e: if return_errno: - return e.errno + rc = socket.connect_ex(self, addr) else: - self._sslobj = None - raise e - self._connected = True - return 0 + rc = None + socket.connect(self, addr) + if not rc: + if self.do_handshake_on_connect: + self.do_handshake() + self._connected = True + return rc + except socket_error: + self._sslobj = None + raise def connect(self, addr): """Connects to remote ADDR, and then wraps the connection in diff --git a/lib-python/2.7/string.py b/lib-python/2.7/string.py --- a/lib-python/2.7/string.py +++ b/lib-python/2.7/string.py @@ -601,12 +601,12 @@ def convert_field(self, value, conversion): # do any conversion on the resulting object - if conversion == 'r': - return repr(value) + if conversion is None: + return value elif conversion == 's': return str(value) - elif conversion is None: - return value + elif conversion == 'r': + return repr(value) raise ValueError("Unknown conversion specifier {0!s}".format(conversion)) diff --git a/lib-python/2.7/subprocess.py b/lib-python/2.7/subprocess.py --- a/lib-python/2.7/subprocess.py +++ b/lib-python/2.7/subprocess.py @@ -671,12 +671,33 @@ c2pread, c2pwrite, errread, errwrite) = self._get_handles(stdin, stdout, stderr) - self._execute_child(args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) + try: + self._execute_child(args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + except Exception: + # Preserve original exception in case os.close raises. + exc_type, exc_value, exc_trace = sys.exc_info() + + to_close = [] + # Only close the pipes we created. + if stdin == PIPE: + to_close.extend((p2cread, p2cwrite)) + if stdout == PIPE: + to_close.extend((c2pread, c2pwrite)) + if stderr == PIPE: + to_close.extend((errread, errwrite)) + + for fd in to_close: + try: + os.close(fd) + except EnvironmentError: + pass + + raise exc_type, exc_value, exc_trace if mswindows: if p2cwrite is not None: @@ -1253,9 +1274,6 @@ if e.errno != errno.ECHILD: raise child_exception = pickle.loads(data) - for fd in (p2cwrite, c2pread, errread): - if fd is not None: - os.close(fd) raise child_exception @@ -1274,7 +1292,7 @@ def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid, - _WNOHANG=os.WNOHANG, _os_error=os.error): + _WNOHANG=os.WNOHANG, _os_error=os.error, _ECHILD=errno.ECHILD): """Check if child process has terminated. Returns returncode attribute. @@ -1287,16 +1305,23 @@ pid, sts = _waitpid(self.pid, _WNOHANG) if pid == self.pid: self._handle_exitstatus(sts) - except _os_error: + except _os_error as e: if _deadstate is not None: self.returncode = _deadstate + if e.errno == _ECHILD: + # This happens if SIGCLD is set to be ignored or + # waiting for child processes has otherwise been + # disabled for our process. This child is dead, we + # can't get the status. + # http://bugs.python.org/issue15756 + self.returncode = 0 return self.returncode def wait(self): """Wait for child process to terminate. Returns returncode attribute.""" - if self.returncode is None: + while self.returncode is None: try: pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0) except OSError as e: @@ -1305,8 +1330,12 @@ # This happens if SIGCLD is set to be ignored or waiting # for child processes has otherwise been disabled for our # process. This child is dead, we can't get the status. + pid = self.pid sts = 0 - self._handle_exitstatus(sts) + # Check the pid and loop as waitpid has been known to return + # 0 even without WNOHANG in odd situations. issue14396. + if pid == self.pid: + self._handle_exitstatus(sts) return self.returncode diff --git a/lib-python/2.7/sysconfig.py b/lib-python/2.7/sysconfig.py --- a/lib-python/2.7/sysconfig.py +++ b/lib-python/2.7/sysconfig.py @@ -116,6 +116,10 @@ if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower(): _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir)) +# set for cross builds +if "_PYTHON_PROJECT_BASE" in os.environ: + # the build directory for posix builds + _PROJECT_BASE = os.path.normpath(os.path.abspath(".")) def is_python_build(): for fn in ("Setup.dist", "Setup.local"): if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)): @@ -445,64 +449,11 @@ srcdir = os.path.join(base, _CONFIG_VARS['srcdir']) _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir) + # OS X platforms require special customization to handle + # multi-architecture, multi-os-version installers if sys.platform == 'darwin': - kernel_version = os.uname()[2] # Kernel version (8.4.3) - major_version = int(kernel_version.split('.')[0]) - - if major_version < 8: - # On Mac OS X before 10.4, check if -arch and -isysroot - # are in CFLAGS or LDFLAGS and remove them if they are. - # This is needed when building extensions on a 10.3 system - # using a universal build of python. - for key in ('LDFLAGS', 'BASECFLAGS', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - flags = _CONFIG_VARS[key] - flags = re.sub('-arch\s+\w+\s', ' ', flags) - flags = re.sub('-isysroot [^ \t]*', ' ', flags) - _CONFIG_VARS[key] = flags - else: - # Allow the user to override the architecture flags using - # an environment variable. - # NOTE: This name was introduced by Apple in OSX 10.5 and - # is used by several scripting languages distributed with - # that OS release. - if 'ARCHFLAGS' in os.environ: - arch = os.environ['ARCHFLAGS'] - for key in ('LDFLAGS', 'BASECFLAGS', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - - flags = _CONFIG_VARS[key] - flags = re.sub('-arch\s+\w+\s', ' ', flags) - flags = flags + ' ' + arch - _CONFIG_VARS[key] = flags - - # If we're on OSX 10.5 or later and the user tries to - # compiles an extension using an SDK that is not present - # on the current machine it is better to not use an SDK - # than to fail. - # - # The major usecase for this is users using a Python.org - # binary installer on OSX 10.6: that installer uses - # the 10.4u SDK, but that SDK is not installed by default - # when you install Xcode. - # - CFLAGS = _CONFIG_VARS.get('CFLAGS', '') - m = re.search('-isysroot\s+(\S+)', CFLAGS) - if m is not None: - sdk = m.group(1) - if not os.path.exists(sdk): - for key in ('LDFLAGS', 'BASECFLAGS', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - - flags = _CONFIG_VARS[key] - flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags) - _CONFIG_VARS[key] = flags + import _osx_support + _osx_support.customize_config_vars(_CONFIG_VARS) if args: vals = [] @@ -560,6 +511,10 @@ return 'win-ia64' return sys.platform + # Set for cross builds explicitly + if "_PYTHON_HOST_PLATFORM" in os.environ: + return os.environ["_PYTHON_HOST_PLATFORM"] + if os.name != "posix" or not hasattr(os, 'uname'): # XXX what about the architecture? NT is Intel or Alpha, # Mac OS is M68k or PPC, etc. @@ -600,91 +555,10 @@ if m: release = m.group() elif osname[:6] == "darwin": - # - # For our purposes, we'll assume that the system version from - # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set - # to. This makes the compatibility story a bit more sane because the - # machine is going to compile and link as if it were - # MACOSX_DEPLOYMENT_TARGET. - cfgvars = get_config_vars() - macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET') - - if 1: - # Always calculate the release of the running machine, - # needed to determine if we can build fat binaries or not. - - macrelease = macver - # Get the system version. Reading this plist is a documented - # way to get the system version (see the documentation for - # the Gestalt Manager) - try: - f = open('/System/Library/CoreServices/SystemVersion.plist') - except IOError: - # We're on a plain darwin box, fall back to the default - # behaviour. - pass - else: - try: - m = re.search( - r'ProductUserVisibleVersion\s*' + - r'(.*?)', f.read()) - if m is not None: - macrelease = '.'.join(m.group(1).split('.')[:2]) - # else: fall back to the default behaviour - finally: - f.close() - - if not macver: - macver = macrelease - - if macver: - release = macver - osname = "macosx" - - if (macrelease + '.') >= '10.4.' and \ - '-arch' in get_config_vars().get('CFLAGS', '').strip(): - # The universal build will build fat binaries, but not on - # systems before 10.4 - # - # Try to detect 4-way universal builds, those have machine-type - # 'universal' instead of 'fat'. - - machine = 'fat' - cflags = get_config_vars().get('CFLAGS') - - archs = re.findall('-arch\s+(\S+)', cflags) - archs = tuple(sorted(set(archs))) - - if len(archs) == 1: - machine = archs[0] - elif archs == ('i386', 'ppc'): - machine = 'fat' - elif archs == ('i386', 'x86_64'): - machine = 'intel' - elif archs == ('i386', 'ppc', 'x86_64'): - machine = 'fat3' - elif archs == ('ppc64', 'x86_64'): - machine = 'fat64' - elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'): - machine = 'universal' - else: - raise ValueError( - "Don't know machine value for archs=%r"%(archs,)) - - elif machine == 'i386': - # On OSX the machine type returned by uname is always the - # 32-bit variant, even if the executable architecture is - # the 64-bit variant - if sys.maxint >= 2**32: - machine = 'x86_64' - - elif machine in ('PowerPC', 'Power_Macintosh'): - # Pick a sane name for the PPC architecture. - # See 'i386' case - if sys.maxint >= 2**32: - machine = 'ppc64' - else: - machine = 'ppc' + import _osx_support + osname, release, machine = _osx_support.get_platform_osx( + get_config_vars(), + osname, release, machine) return "%s-%s-%s" % (osname, release, machine) diff --git a/lib-python/2.7/tarfile.py b/lib-python/2.7/tarfile.py --- a/lib-python/2.7/tarfile.py +++ b/lib-python/2.7/tarfile.py @@ -1987,9 +1987,8 @@ # Append the tar header and data to the archive. if tarinfo.isreg(): - f = bltn_open(name, "rb") - self.addfile(tarinfo, f) - f.close() + with bltn_open(name, "rb") as f: + self.addfile(tarinfo, f) elif tarinfo.isdir(): self.addfile(tarinfo) @@ -2197,10 +2196,11 @@ """Make a file called targetpath. """ source = self.extractfile(tarinfo) - target = bltn_open(targetpath, "wb") - copyfileobj(source, target) - source.close() - target.close() + try: + with bltn_open(targetpath, "wb") as target: + copyfileobj(source, target) + finally: + source.close() def makeunknown(self, tarinfo, targetpath): """Make a file from a TarInfo object with an unknown type @@ -2397,7 +2397,7 @@ """ if tarinfo.issym(): # Always search the entire archive. - linkname = os.path.dirname(tarinfo.name) + "/" + tarinfo.linkname + linkname = "/".join(filter(None, (os.path.dirname(tarinfo.name), tarinfo.linkname))) limit = None else: # Search the archive before the link, because a hard link is diff --git a/lib-python/2.7/telnetlib.py b/lib-python/2.7/telnetlib.py --- a/lib-python/2.7/telnetlib.py +++ b/lib-python/2.7/telnetlib.py @@ -34,6 +34,7 @@ # Imported modules +import errno import sys import socket import select @@ -205,6 +206,7 @@ self.sb = 0 # flag for SB and SE sequence. self.sbdataq = '' self.option_callback = None + self._has_poll = hasattr(select, 'poll') if host is not None: self.open(host, port, timeout) @@ -287,6 +289,61 @@ is closed and no cooked data is available. """ + if self._has_poll: + return self._read_until_with_poll(match, timeout) + else: + return self._read_until_with_select(match, timeout) + + def _read_until_with_poll(self, match, timeout): + """Read until a given string is encountered or until timeout. + + This method uses select.poll() to implement the timeout. + """ + n = len(match) + call_timeout = timeout + if timeout is not None: + from time import time + time_start = time() + self.process_rawq() + i = self.cookedq.find(match) + if i < 0: + poller = select.poll() + poll_in_or_priority_flags = select.POLLIN | select.POLLPRI + poller.register(self, poll_in_or_priority_flags) + while i < 0 and not self.eof: + try: + ready = poller.poll(call_timeout) + except select.error as e: + if e.errno == errno.EINTR: + if timeout is not None: + elapsed = time() - time_start + call_timeout = timeout-elapsed + continue + raise + for fd, mode in ready: + if mode & poll_in_or_priority_flags: + i = max(0, len(self.cookedq)-n) + self.fill_rawq() + self.process_rawq() + i = self.cookedq.find(match, i) + if timeout is not None: + elapsed = time() - time_start + if elapsed >= timeout: + break + call_timeout = timeout-elapsed + poller.unregister(self) + if i >= 0: + i = i + n + buf = self.cookedq[:i] + self.cookedq = self.cookedq[i:] + return buf + return self.read_very_lazy() + + def _read_until_with_select(self, match, timeout=None): + """Read until a given string is encountered or until timeout. + + The timeout is implemented using select.select(). + """ n = len(match) self.process_rawq() i = self.cookedq.find(match) @@ -589,6 +646,79 @@ results are undeterministic, and may depend on the I/O timing. """ + if self._has_poll: + return self._expect_with_poll(list, timeout) + else: + return self._expect_with_select(list, timeout) + + def _expect_with_poll(self, expect_list, timeout=None): + """Read until one from a list of a regular expressions matches. + + This method uses select.poll() to implement the timeout. + """ + re = None + expect_list = expect_list[:] + indices = range(len(expect_list)) + for i in indices: + if not hasattr(expect_list[i], "search"): + if not re: import re + expect_list[i] = re.compile(expect_list[i]) + call_timeout = timeout + if timeout is not None: + from time import time + time_start = time() + self.process_rawq() + m = None + for i in indices: + m = expect_list[i].search(self.cookedq) + if m: + e = m.end() + text = self.cookedq[:e] + self.cookedq = self.cookedq[e:] + break + if not m: + poller = select.poll() + poll_in_or_priority_flags = select.POLLIN | select.POLLPRI + poller.register(self, poll_in_or_priority_flags) + while not m and not self.eof: + try: + ready = poller.poll(call_timeout) + except select.error as e: + if e.errno == errno.EINTR: + if timeout is not None: + elapsed = time() - time_start + call_timeout = timeout-elapsed + continue + raise + for fd, mode in ready: + if mode & poll_in_or_priority_flags: + self.fill_rawq() + self.process_rawq() + for i in indices: + m = expect_list[i].search(self.cookedq) + if m: + e = m.end() + text = self.cookedq[:e] + self.cookedq = self.cookedq[e:] + break + if timeout is not None: + elapsed = time() - time_start + if elapsed >= timeout: + break + call_timeout = timeout-elapsed + poller.unregister(self) + if m: + return (i, m, text) + text = self.read_very_lazy() + if not text and self.eof: + raise EOFError + return (-1, None, text) + + def _expect_with_select(self, list, timeout=None): + """Read until one from a list of a regular expressions matches. + + The timeout is implemented using select.select(). + """ re = None list = list[:] indices = range(len(list)) diff --git a/lib-python/2.7/tempfile.py b/lib-python/2.7/tempfile.py --- a/lib-python/2.7/tempfile.py +++ b/lib-python/2.7/tempfile.py @@ -29,6 +29,7 @@ # Imports. +import io as _io import os as _os import errno as _errno from random import Random as _Random @@ -193,15 +194,18 @@ name = namer.next() filename = _os.path.join(dir, name) try: - fd = _os.open(filename, flags, 0600) - fp = _os.fdopen(fd, 'w') - fp.write('blat') - fp.close() - _os.unlink(filename) - del fp, fd + fd = _os.open(filename, flags, 0o600) + try: + try: + with _io.open(fd, 'wb', closefd=False) as fp: + fp.write(b'blat') + finally: + _os.close(fd) + finally: + _os.unlink(filename) return dir - except (OSError, IOError), e: - if e[0] != _errno.EEXIST: + except (OSError, IOError) as e: + if e.args[0] != _errno.EEXIST: break # no point trying more names in this directory pass raise IOError, (_errno.ENOENT, @@ -546,10 +550,6 @@ def closed(self): return self._file.closed - @property - def encoding(self): - return self._file.encoding - def fileno(self): self.rollover() return self._file.fileno() @@ -562,15 +562,17 @@ @property def mode(self): - return self._file.mode + try: + return self._file.mode + except AttributeError: + return self._TemporaryFileArgs[0] @property def name(self): - return self._file.name - - @property - def newlines(self): - return self._file.newlines + try: + return self._file.name + except AttributeError: + return None def next(self): return self._file.next @@ -610,4 +612,7 @@ return rv def xreadlines(self, *args): - return self._file.xreadlines(*args) + try: + return self._file.xreadlines(*args) + except AttributeError: + return iter(self._file.readlines(*args)) diff --git a/lib-python/2.7/test/keycert.pem b/lib-python/2.7/test/keycert.pem --- a/lib-python/2.7/test/keycert.pem +++ b/lib-python/2.7/test/keycert.pem @@ -1,32 +1,31 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L -opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH -fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB -AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU -D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA -IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM -oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 -ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ -loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j -oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA -z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq -ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV -q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANtb0+YrKuxevGpm +LrjaUhZSgz6zFAmuGFmKmUbdjmfv9zSmmdsQIksK++jK0Be9LeZy20j6ahOfuVa0 +ufEmPoP7Fy4hXegKZR9cCWcIe/A6H2xWF1IIJLRTLaU8ol/I7T+um5HD5AwAwNPP +USNU0Eegmvp+xxWu3NX2m1Veot85AgMBAAECgYA3ZdZ673X0oexFlq7AAmrutkHt +CL7LvwrpOiaBjhyTxTeSNWzvtQBkIU8DOI0bIazA4UreAFffwtvEuPmonDb3F+Iq +SMAu42XcGyVZEl+gHlTPU9XRX7nTOXVt+MlRRRxL6t9GkGfUAXI3XxJDXW3c0vBK +UL9xqD8cORXOfE06rQJBAP8mEX1ERkR64Ptsoe4281vjTlNfIbs7NMPkUnrn9N/Y +BLhjNIfQ3HFZG8BTMLfX7kCS9D593DW5tV4Z9BP/c6cCQQDcFzCcVArNh2JSywOQ +ZfTfRbJg/Z5Lt9Fkngv1meeGNPgIMLN8Sg679pAOOWmzdMO3V706rNPzSVMME7E5 +oPIfAkEA8pDddarP5tCvTTgUpmTFbakm0KoTZm2+FzHcnA4jRh+XNTjTOv98Y6Ik +eO5d1ZnKXseWvkZncQgxfdnMqqpj5wJAcNq/RVne1DbYlwWchT2Si65MYmmJ8t+F +0mcsULqjOnEMwf5e+ptq5LzwbyrHZYq5FNk7ocufPv/ZQrcSSC+cFwJBAKvOJByS +x56qyGeZLOQlWS2JS3KJo59XuLFGqcbgN9Om9xFa41Yb4N9NvplFivsvZdw3m1Q/ +SPIXQuT8RMPDVNQ= +-----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD -VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x -IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT -U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 -NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl -bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m -dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj -aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh -m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 -M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn -fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC -AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb -08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx -CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ -iHkC6gGdBJhogs4= +MIICVDCCAb2gAwIBAgIJANfHOBkZr8JOMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHEw5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMDEw +MDgyMzAxNTZaFw0yMDEwMDUyMzAxNTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH +Ew5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9uIFNvZnR3YXJlIEZvdW5k +YXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEA21vT5isq7F68amYuuNpSFlKDPrMUCa4YWYqZRt2OZ+/3NKaZ2xAiSwr7 +6MrQF70t5nLbSPpqE5+5VrS58SY+g/sXLiFd6AplH1wJZwh78DofbFYXUggktFMt +pTyiX8jtP66bkcPkDADA089RI1TQR6Ca+n7HFa7c1fabVV6i3zkCAwEAAaMYMBYw +FAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBBQUAA4GBAHPctQBEQ4wd +BJ6+JcpIraopLn8BGhbjNWj40mmRqWB/NAWF6M5ne7KpGAu7tLeG4hb1zLaldK8G +lxy2GPSRF6LFS48dpEj2HbMv2nvv6xxalDMJ9+DicWgAKTQ6bcX2j3GUkCR0g/T1 +CRlNBAAlvhKzO7Clpf9l0YKBEfraJByX -----END CERTIFICATE----- diff --git a/lib-python/2.7/test/pickletester.py b/lib-python/2.7/test/pickletester.py --- a/lib-python/2.7/test/pickletester.py +++ b/lib-python/2.7/test/pickletester.py @@ -6,7 +6,8 @@ import pickletools import copy_reg -from test.test_support import TestFailed, have_unicode, TESTFN +from test.test_support import (TestFailed, have_unicode, TESTFN, _2G, _1M, + precisionbigmemtest) # Tests that try a number of pickle protocols should have a # for proto in protocols: @@ -502,10 +503,10 @@ i = C() i.attr = i for proto in protocols: - s = self.dumps(i, 2) + s = self.dumps(i, proto) x = self.loads(s) self.assertEqual(dir(x), dir(i)) - self.assertTrue(x.attr is x) + self.assertIs(x.attr, x) def test_recursive_multi(self): l = [] @@ -1280,3 +1281,31 @@ f.write(pickled2) f.seek(0) self.assertEqual(unpickler.load(), data2) + +class BigmemPickleTests(unittest.TestCase): + + # Memory requirements: 1 byte per character for input strings, 1 byte + # for pickled data, 1 byte for unpickled strings, 1 byte for internal + # buffer and 1 byte of free space for resizing of internal buffer. + + @precisionbigmemtest(size=_2G + 100*_1M, memuse=5) + def test_huge_strlist(self, size): + chunksize = 2**20 + data = [] + while size > chunksize: + data.append('x' * chunksize) + size -= chunksize + chunksize += 1 + data.append('y' * size) + + try: + for proto in protocols: + try: + pickled = self.dumps(data, proto) + res = self.loads(pickled) + self.assertEqual(res, data) + finally: + res = None + pickled = None + finally: + data = None diff --git a/lib-python/2.7/test/regrtest.py b/lib-python/2.7/test/regrtest.py --- a/lib-python/2.7/test/regrtest.py +++ b/lib-python/2.7/test/regrtest.py @@ -32,7 +32,7 @@ Selecting tests --r/--random -- randomize test execution order (see below) +-r/--randomize -- randomize test execution order (see below) --randseed -- pass a random seed to reproduce a previous random run -f/--fromfile -- read names of tests to run from a file (see below) -x/--exclude -- arguments are tests to *exclude* @@ -158,6 +158,7 @@ import os import random import re +import shutil import sys import time import traceback @@ -258,7 +259,7 @@ try: opts, args = getopt.getopt(sys.argv[1:], 'hvqxsSrf:lu:t:TD:NLR:FwWM:j:', ['help', 'verbose', 'verbose2', 'verbose3', 'quiet', - 'exclude', 'single', 'slow', 'random', 'fromfile', 'findleaks', + 'exclude', 'single', 'slow', 'randomize', 'fromfile=', 'findleaks', 'use=', 'threshold=', 'trace', 'coverdir=', 'nocoverdir', 'runleaks', 'huntrleaks=', 'memlimit=', 'randseed=', 'multiprocess=', 'slaveargs=', 'forever', 'header']) @@ -540,6 +541,8 @@ print stdout if stderr: print >>sys.stderr, stderr + sys.stdout.flush() + sys.stderr.flush() if result[0] == INTERRUPTED: assert result[1] == 'KeyboardInterrupt' raise KeyboardInterrupt # What else? @@ -941,7 +944,6 @@ return FAILED, test_time def cleanup_test_droppings(testname, verbose): - import shutil import stat import gc diff --git a/lib-python/2.7/test/script_helper.py b/lib-python/2.7/test/script_helper.py --- a/lib-python/2.7/test/script_helper.py +++ b/lib-python/2.7/test/script_helper.py @@ -10,7 +10,13 @@ import py_compile import contextlib import shutil -import zipfile +try: + import zipfile +except ImportError: + # If Python is build without Unicode support, importing _io will + # fail, which, in turn, means that zipfile cannot be imported + # Most of this module can then still be used. + pass from test.test_support import strip_python_stderr diff --git a/lib-python/2.7/test/sha256.pem b/lib-python/2.7/test/sha256.pem --- a/lib-python/2.7/test/sha256.pem +++ b/lib-python/2.7/test/sha256.pem @@ -1,129 +1,128 @@ # Certificate chain for https://sha256.tbs-internet.com - 0 s:/C=FR/postalCode=14000/ST=Calvados/L=CAEN/street=22 rue de Bretagne/O=TBS INTERNET/OU=0002 440443810/OU=sha-256 production/CN=sha256.tbs-internet.com - i:/C=FR/ST=Calvados/L=Caen/O=TBS INTERNET/OU=Terms and Conditions: http://www.tbs-internet.com/CA/repository/OU=TBS INTERNET CA/CN=TBS X509 CA SGC + 0 s:/C=FR/postalCode=14000/ST=Calvados/L=CAEN/street=22 rue de Bretagne/O=TBS INTERNET/OU=0002 440443810/OU=Certificats TBS X509/CN=ecom.tbs-x509.com + i:/C=FR/ST=Calvados/L=Caen/O=TBS INTERNET/OU=Terms and Conditions: http://www.tbs-internet.com/CA/repository/OU=TBS INTERNET CA/CN=TBS X509 CA business -----BEGIN CERTIFICATE----- -MIIGXTCCBUWgAwIBAgIRAMmag+ygSAdxZsbyzYjhuW0wDQYJKoZIhvcNAQELBQAw -gcQxCzAJBgNVBAYTAkZSMREwDwYDVQQIEwhDYWx2YWRvczENMAsGA1UEBxMEQ2Fl +MIIGTjCCBTagAwIBAgIQOh3d9dNDPq1cSdJmEiMpqDANBgkqhkiG9w0BAQUFADCB +yTELMAkGA1UEBhMCRlIxETAPBgNVBAgTCENhbHZhZG9zMQ0wCwYDVQQHEwRDYWVu +MRUwEwYDVQQKEwxUQlMgSU5URVJORVQxSDBGBgNVBAsTP1Rlcm1zIGFuZCBDb25k +aXRpb25zOiBodHRwOi8vd3d3LnRicy1pbnRlcm5ldC5jb20vQ0EvcmVwb3NpdG9y +eTEYMBYGA1UECxMPVEJTIElOVEVSTkVUIENBMR0wGwYDVQQDExRUQlMgWDUwOSBD +QSBidXNpbmVzczAeFw0xMTAxMjUwMDAwMDBaFw0xMzAyMDUyMzU5NTlaMIHHMQsw +CQYDVQQGEwJGUjEOMAwGA1UEERMFMTQwMDAxETAPBgNVBAgTCENhbHZhZG9zMQ0w +CwYDVQQHEwRDQUVOMRswGQYDVQQJExIyMiBydWUgZGUgQnJldGFnbmUxFTATBgNV +BAoTDFRCUyBJTlRFUk5FVDEXMBUGA1UECxMOMDAwMiA0NDA0NDM4MTAxHTAbBgNV +BAsTFENlcnRpZmljYXRzIFRCUyBYNTA5MRowGAYDVQQDExFlY29tLnRicy14NTA5 +LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKRrlHUnJ++1lpcg +jtYco7cdmRe+EEfTmwPfCdfV3G1QfsTSvY6FfMpm/83pqHfT+4ANwr18wD9ZrAEN +G16mf9VdCGK12+TP7DmqeZyGIqlFFoahQnmb8EarvE43/1UeQ2CV9XmzwZvpqeli +LfXsFonawrY3H6ZnMwS64St61Z+9gdyuZ/RbsoZBbT5KUjDEG844QRU4OT1IGeEI +eY5NM5RNIh6ZNhVtqeeCxMS7afONkHQrOco73RdSTRck/Hj96Ofl3MHNHryr+AMK +DGFk1kLCZGpPdXtkxXvaDeQoiYDlil26CWc+YK6xyDPMdsWvoG14ZLyCpzMXA7/7 +4YAQRH0CAwEAAaOCAjAwggIsMB8GA1UdIwQYMBaAFBoJBMz5CY+7HqDO1KQUf0vV +I1jNMB0GA1UdDgQWBBQgOU8HsWzbmD4WZP5Wtdw7jca2WDAOBgNVHQ8BAf8EBAMC +BaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +TAYDVR0gBEUwQzBBBgsrBgEEAYDlNwIBATAyMDAGCCsGAQUFBwIBFiRodHRwczov +L3d3dy50YnMtaW50ZXJuZXQuY29tL0NBL0NQUzEwdwYDVR0fBHAwbjA3oDWgM4Yx +aHR0cDovL2NybC50YnMtaW50ZXJuZXQuY29tL1RCU1g1MDlDQWJ1c2luZXNzLmNy +bDAzoDGgL4YtaHR0cDovL2NybC50YnMteDUwOS5jb20vVEJTWDUwOUNBYnVzaW5l +c3MuY3JsMIGwBggrBgEFBQcBAQSBozCBoDA9BggrBgEFBQcwAoYxaHR0cDovL2Ny +dC50YnMtaW50ZXJuZXQuY29tL1RCU1g1MDlDQWJ1c2luZXNzLmNydDA5BggrBgEF +BQcwAoYtaHR0cDovL2NydC50YnMteDUwOS5jb20vVEJTWDUwOUNBYnVzaW5lc3Mu +Y3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC50YnMteDUwOS5jb20wMwYDVR0R +BCwwKoIRZWNvbS50YnMteDUwOS5jb22CFXd3dy5lY29tLnRicy14NTA5LmNvbTAN +BgkqhkiG9w0BAQUFAAOCAQEArT4NHfbY87bGAw8lPV4DmHlmuDuVp/y7ltO3Ynse +3Rz8RxW2AzuO0Oy2F0Cu4yWKtMyEyMXyHqWtae7ElRbdTu5w5GwVBLJHClCzC8S9 +SpgMMQTx3Rgn8vjkHuU9VZQlulZyiPK7yunjc7c310S9FRZ7XxOwf8Nnx4WnB+No +WrfApzhhQl31w+RyrNxZe58hCfDDHmevRvwLjQ785ZoQXJDj2j3qAD4aI2yB8lB5 +oaE1jlCJzC7Kmz/Y9jzfmv/zAs1LQTm9ktevv4BTUFaGjv9jxnQ1xnS862ZiouLW +zZYIlYPf4F6JjXGiIQgQRglILUfq3ftJd9/ok9W9ZF8h8w== +-----END CERTIFICATE----- + 1 s:/C=FR/ST=Calvados/L=Caen/O=TBS INTERNET/OU=Terms and Conditions: http://www.tbs-internet.com/CA/repository/OU=TBS INTERNET CA/CN=TBS X509 CA business + i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root +-----BEGIN CERTIFICATE----- +MIIFPzCCBCegAwIBAgIQDlBz/++iRSmLDeVRHT/hADANBgkqhkiG9w0BAQUFADBv +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk +ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF +eHRlcm5hbCBDQSBSb290MB4XDTA1MTIwMTAwMDAwMFoXDTE5MDcwOTE4MTkyMlow +gckxCzAJBgNVBAYTAkZSMREwDwYDVQQIEwhDYWx2YWRvczENMAsGA1UEBxMEQ2Fl bjEVMBMGA1UEChMMVEJTIElOVEVSTkVUMUgwRgYDVQQLEz9UZXJtcyBhbmQgQ29u ZGl0aW9uczogaHR0cDovL3d3dy50YnMtaW50ZXJuZXQuY29tL0NBL3JlcG9zaXRv -cnkxGDAWBgNVBAsTD1RCUyBJTlRFUk5FVCBDQTEYMBYGA1UEAxMPVEJTIFg1MDkg -Q0EgU0dDMB4XDTEwMDIxODAwMDAwMFoXDTEyMDIxOTIzNTk1OVowgcsxCzAJBgNV -BAYTAkZSMQ4wDAYDVQQREwUxNDAwMDERMA8GA1UECBMIQ2FsdmFkb3MxDTALBgNV -BAcTBENBRU4xGzAZBgNVBAkTEjIyIHJ1ZSBkZSBCcmV0YWduZTEVMBMGA1UEChMM -VEJTIElOVEVSTkVUMRcwFQYDVQQLEw4wMDAyIDQ0MDQ0MzgxMDEbMBkGA1UECxMS -c2hhLTI1NiBwcm9kdWN0aW9uMSAwHgYDVQQDExdzaGEyNTYudGJzLWludGVybmV0 -LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbuM8VT7f0nntwu -N3F7v9KIBlhKNAxqCrziOXU5iqUt8HrQB3DtHbdmII+CpVUlwlmepsx6G+srEZ9a -MIGAy0nxi5aLb7watkyIdPjJTMvTUBQ/+RPWzt5JtYbbY9BlJ+yci0dctP74f4NU -ISLtlrEjUbf2gTohLrcE01TfmOF6PDEbB5PKDi38cB3NzKfizWfrOaJW6Q1C1qOJ -y4/4jkUREX1UFUIxzx7v62VfjXSGlcjGpBX1fvtABQOSLeE0a6gciDZs1REqroFf -5eXtqYphpTa14Z83ITXMfgg5Nze1VtMnzI9Qx4blYBw4dgQVEuIsYr7FDBOITDzc -VEVXZx0CAwEAAaOCAj8wggI7MB8GA1UdIwQYMBaAFAdEdoWTKLx/bXjSCuv6TEvf -2YIfMB0GA1UdDgQWBBSJKI/AYVI9RQNY0QPIqc8ej2QivTAOBgNVHQ8BAf8EBAMC -BaAwDAYDVR0TAQH/BAIwADA0BgNVHSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIG -CisGAQQBgjcKAwMGCWCGSAGG+EIEATBMBgNVHSAERTBDMEEGCysGAQQBgOU3AgQB -MDIwMAYIKwYBBQUHAgEWJGh0dHBzOi8vd3d3LnRicy1pbnRlcm5ldC5jb20vQ0Ev -Q1BTNDBtBgNVHR8EZjBkMDKgMKAuhixodHRwOi8vY3JsLnRicy1pbnRlcm5ldC5j -b20vVEJTWDUwOUNBU0dDLmNybDAuoCygKoYoaHR0cDovL2NybC50YnMteDUwOS5j -b20vVEJTWDUwOUNBU0dDLmNybDCBpgYIKwYBBQUHAQEEgZkwgZYwOAYIKwYBBQUH -MAKGLGh0dHA6Ly9jcnQudGJzLWludGVybmV0LmNvbS9UQlNYNTA5Q0FTR0MuY3J0 -MDQGCCsGAQUFBzAChihodHRwOi8vY3J0LnRicy14NTA5LmNvbS9UQlNYNTA5Q0FT -R0MuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC50YnMteDUwOS5jb20wPwYD -VR0RBDgwNoIXc2hhMjU2LnRicy1pbnRlcm5ldC5jb22CG3d3dy5zaGEyNTYudGJz -LWludGVybmV0LmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAA5NL0D4QSqhErhlkdPmz -XtiMvdGL+ZehM4coTRIpasM/Agt36Rc0NzCvnQwKE+wkngg1Gy2qe7Q0E/ziqBtB -fZYzdVgu1zdiL4kTaf+wFKYAFGsFbyeEmXysy+CMwaNoF2vpSjCU1UD56bEnTX/W -fxVZYxtBQUpnu2wOsm8cDZuZRv9XrYgAhGj9Tt6F0aVHSDGn59uwShG1+BVF/uju -SCyPTTjL1oc7YElJUzR/x4mQJYvtQI8gDIDAGEOs7v3R/gKa5EMfbUQUI4C84UbI -Yz09Jdnws/MkC/Hm1BZEqk89u7Hvfv+oHqEb0XaUo0TDfsxE0M1sMdnLb91QNQBm -UQ== ------END CERTIFICATE----- - 1 s:/C=FR/ST=Calvados/L=Caen/O=TBS INTERNET/OU=Terms and Conditions: http://www.tbs-internet.com/CA/repository/OU=TBS INTERNET CA/CN=TBS X509 CA SGC - i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root ------BEGIN CERTIFICATE----- -MIIFVjCCBD6gAwIBAgIQXpDZ0ETJMV02WTx3GTnhhTANBgkqhkiG9w0BAQUFADBv -MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk -ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF -eHRlcm5hbCBDQSBSb290MB4XDTA1MTIwMTAwMDAwMFoXDTE5MDYyNDE5MDYzMFow -gcQxCzAJBgNVBAYTAkZSMREwDwYDVQQIEwhDYWx2YWRvczENMAsGA1UEBxMEQ2Fl -bjEVMBMGA1UEChMMVEJTIElOVEVSTkVUMUgwRgYDVQQLEz9UZXJtcyBhbmQgQ29u -ZGl0aW9uczogaHR0cDovL3d3dy50YnMtaW50ZXJuZXQuY29tL0NBL3JlcG9zaXRv -cnkxGDAWBgNVBAsTD1RCUyBJTlRFUk5FVCBDQTEYMBYGA1UEAxMPVEJTIFg1MDkg -Q0EgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsgOkO3f7wzN6 -rOjg45tR5vjBfzK7qmV9IBxb/QW9EEXxG+E7FNhZqQLtwGBKoSsHTnQqV75wWMk0 -9tinWvftBkSpj5sTi/8cbzJfUvTSVYh3Qxv6AVVjMMH/ruLjE6y+4PoaPs8WoYAQ -ts5R4Z1g8c/WnTepLst2x0/Wv7GmuoQi+gXvHU6YrBiu7XkeYhzc95QdviWSJRDk -owhb5K43qhcvjRmBfO/paGlCliDGZp8mHwrI21mwobWpVjTxZRwYO3bd4+TGcI4G -Ie5wmHwE8F7SK1tgSqbBacKjDa93j7txKkfz/Yd2n7TGqOXiHPsJpG655vrKtnXk -9vs1zoDeJQIDAQABo4IBljCCAZIwHQYDVR0OBBYEFAdEdoWTKLx/bXjSCuv6TEvf -2YIfMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMCAGA1UdJQQZ -MBcGCisGAQQBgjcKAwMGCWCGSAGG+EIEATAYBgNVHSAEETAPMA0GCysGAQQBgOU3 -AgQBMHsGA1UdHwR0MHIwOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0Fk -ZFRydXN0RXh0ZXJuYWxDQVJvb3QuY3JsMDagNKAyhjBodHRwOi8vY3JsLmNvbW9k -by5uZXQvQWRkVHJ1c3RFeHRlcm5hbENBUm9vdC5jcmwwgYAGCCsGAQUFBwEBBHQw -cjA4BggrBgEFBQcwAoYsaHR0cDovL2NydC5jb21vZG9jYS5jb20vQWRkVHJ1c3RV -VE5TR0NDQS5jcnQwNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuY29tb2RvLm5ldC9B -ZGRUcnVzdFVUTlNHQ0NBLmNydDARBglghkgBhvhCAQEEBAMCAgQwDQYJKoZIhvcN -AQEFBQADggEBAK2zEzs+jcIrVK9oDkdDZNvhuBYTdCfpxfFs+OAujW0bIfJAy232 -euVsnJm6u/+OrqKudD2tad2BbejLLXhMZViaCmK7D9nrXHx4te5EP8rL19SUVqLY -1pTnv5dhNgEgvA7n5lIzDSYs7yRLsr7HJsYPr6SeYSuZizyX1SNz7ooJ32/F3X98 -RB0Mlc/E0OyOrkQ9/y5IrnpnaSora8CnUrV5XNOg+kyCz9edCyx4D5wXYcwZPVWz -8aDqquESrezPyjtfi4WRO4s/VD3HLZvOxzMrWAVYCDG9FxaOhF0QGuuG1F7F3GKV -v6prNyCl016kRl2j1UT+a7gLd8fA25A4C9E= +cnkxGDAWBgNVBAsTD1RCUyBJTlRFUk5FVCBDQTEdMBsGA1UEAxMUVEJTIFg1MDkg +Q0EgYnVzaW5lc3MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDB1PAU +qudCcz3tmyGcf+u6EkZqonKKHrV4gZYbvVkIRojmmlhfi/jwvpHvo8bqSt/9Rj5S +jhCDW0pcbI+IPPtD1Jy+CHNSfnMqVDy6CKQ3p5maTzCMG6ZT+XjnvcND5v+FtaiB +xk1iCX6uvt0jeUtdZvYbyytsSDE6c3Y5//wRxOF8tM1JxibwO3pyER26jbbN2gQz +m/EkdGjLdJ4svPk23WDAvQ6G0/z2LcAaJB+XLfqRwfQpHQvfKa1uTi8PivC8qtip +rmNQMMPMjxSK2azX8cKjjTDJiUKaCb4VHlJDWKEsCFRpgJAoAuX8f7Yfs1M4esGo +sWb3PGspK3O22uIlAgMBAAGjggF6MIIBdjAdBgNVHQ4EFgQUGgkEzPkJj7seoM7U +pBR/S9UjWM0wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwGAYD +VR0gBBEwDzANBgsrBgEEAYDlNwIBATB7BgNVHR8EdDByMDigNqA0hjJodHRwOi8v +Y3JsLmNvbW9kb2NhLmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDA2oDSg +MoYwaHR0cDovL2NybC5jb21vZG8ubmV0L0FkZFRydXN0RXh0ZXJuYWxDQVJvb3Qu +Y3JsMIGGBggrBgEFBQcBAQR6MHgwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29t +b2RvY2EuY29tL0FkZFRydXN0VVROU2VydmVyQ0EuY3J0MDkGCCsGAQUFBzAChi1o +dHRwOi8vY3J0LmNvbW9kby5uZXQvQWRkVHJ1c3RVVE5TZXJ2ZXJDQS5jcnQwEQYJ +YIZIAYb4QgEBBAQDAgIEMA0GCSqGSIb3DQEBBQUAA4IBAQA7mqrMgk/MrE6QnbNA +h4nRCn2ti4bg4w2C3lB6bSvRPnYwuNw9Jb8vuKkNFzRDxNJXqVDZdfFW5CVQJuyd +nfAx83+wk+spzvFaE1KhFYfN9G9pQfXUfvDRoIcJgPEKUXL1wRiOG+IjU3VVI8pg +IgqHkr7ylln5i5zCiFAPuIJmYUSFg/gxH5xkCNcjJqqrHrHatJr6Qrrke93joupw +oU1njfAcZtYp6fbiK6u2b1pJqwkVBE8RsfLnPhRj+SFbpvjv8Od7o/ieJhFIYQNU +k2jX2u8qZnAiNw93LZW9lpYjtuvMXq8QQppENNja5b53q7UwI+lU7ZGjZ7quuESp +J6/5 -----END CERTIFICATE----- 2 s:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root - i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC + i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware -----BEGIN CERTIFICATE----- -MIIEZjCCA06gAwIBAgIQUSYKkxzif5zDpV954HKugjANBgkqhkiG9w0BAQUFADCB -kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +MIIETzCCAzegAwIBAgIQHM5EYpUZep1jUvnyI6m2mDANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw -IFNHQzAeFw0wNTA2MDcwODA5MTBaFw0xOTA2MjQxOTA2MzBaMG8xCzAJBgNVBAYT -AlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0 -ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB -IFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC39xoz5vIABC05 -4E5b7R+8bA/Ntfojts7emxEzl6QpTH2Tn71KvJPtAxrjj8/lbVBa1pcplFqAsEl6 -2y6V/bjKvzc4LR4+kUGtcFbH8E8/6DKedMrIkFTpxl8PeJ2aQDwOrGGqXhSPnoeh -alDc15pOrwWzpnGUnHGzUGAKxxOdOAeGAqjpqGkmGJCrTLBPI6s6T4TY386f4Wlv -u9dC12tE5Met7m1BX3JacQg3s3llpFmglDf3AC8NwpJy2tA4ctsUqEXEXSp9t7TW -xO6szRNEt8kr3UMAJfphuWlqWCMRt6czj1Z1WfXNKddGtworZbbTQm8Vsrh7++/p -XVPVNFonAgMBAAGjgdgwgdUwHwYDVR0jBBgwFoAUUzLRs89/+uDxoF2FTpLSnkUd -tE8wHQYDVR0OBBYEFK29mHo0tCb3+sQmVO8DveAky1QaMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MBEGCWCGSAGG+EIBAQQEAwIBAjAgBgNVHSUEGTAX -BgorBgEEAYI3CgMDBglghkgBhvhCBAEwPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDov -L2NybC51c2VydHJ1c3QuY29tL1VUTi1EQVRBQ29ycFNHQy5jcmwwDQYJKoZIhvcN -AQEFBQADggEBAMbuUxdoFLJRIh6QWA2U/b3xcOWGLcM2MY9USEbnLQg3vGwKYOEO -rVE04BKT6b64q7gmtOmWPSiPrmQH/uAB7MXjkesYoPF1ftsK5p+R26+udd8jkWjd -FwBaS/9kbHDrARrQkNnHptZt9hPk/7XJ0h4qy7ElQyZ42TCbTg0evmnv3+r+LbPM -+bDdtRTKkdSytaX7ARmjR3mfnYyVhzT4HziS2jamEfpr62vp3EV4FTkG101B5CHI -3C+H0be/SGB1pWLLJN47YaApIKa+xWycxOkKaSLvkTr6Jq/RW0GnOuL4OAdCq8Fb -+M5tug8EPzI0rNwEKNdwMBQmBsTkm5jVz3g= +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNMDUwNjA3MDgwOTEwWhcNMTkwNzA5MTgxOTIyWjBvMQswCQYD +VQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0 +IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5h +bCBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt/caM+by +AAQtOeBOW+0fvGwPzbX6I7bO3psRM5ekKUx9k5+9SryT7QMa44/P5W1QWtaXKZRa +gLBJetsulf24yr83OC0ePpFBrXBWx/BPP+gynnTKyJBU6cZfD3idmkA8Dqxhql4U +j56HoWpQ3NeaTq8Fs6ZxlJxxs1BgCscTnTgHhgKo6ahpJhiQq0ywTyOrOk+E2N/O +n+Fpb7vXQtdrROTHre5tQV9yWnEIN7N5ZaRZoJQ39wAvDcKSctrQOHLbFKhFxF0q +fbe01sTurM0TRLfJK91DACX6YblpalgjEbenM49WdVn1zSnXRrcKK2W200JvFbK4 +e/vv6V1T1TRaJwIDAQABo4G9MIG6MB8GA1UdIwQYMBaAFKFyXyYbKJhDlV0HN9WF +lp1L0sNFMB0GA1UdDgQWBBStvZh6NLQm9/rEJlTvA73gJMtUGjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAQIwRAYDVR0f +BD0wOzA5oDegNYYzaHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmly +c3QtSGFyZHdhcmUuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQByQhANOs4kClrwF8BW +onvUOGCSjRK52zYZgDXYNjDtmr5rJ6NyPFDNn+JxkLpjYetIFMTbSRe679Bt8m7a +gIAoQYFQtxMuyLnJegB2aEbQiIxh/tC21UcFF7ktdnDoTlA6w3pLuvunaI84Of3o +2YBrhzkTbCfaYk5JRlTpudW9DkUkHBsyx3nknPKnplkIGaK0jgn8E0n+SFabYaHk +I9LroYT/+JtLefh9lgBdAgVv0UPbzoGfuDsrk/Zh+UrgbLFpHoVnElhzbkh64Z0X +OGaJunQc68cCZu5HTn/aK7fBGMcVflRCXLVEQpU9PIAdGA8Ynvg684t8GMaKsRl1 +jIGZ -----END CERTIFICATE----- - 3 s:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC - i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC + 3 s:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware + i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware -----BEGIN CERTIFICATE----- -MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB -kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw -IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG -EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD -VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu -dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 -E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ -D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK -4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq -lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW -bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB -o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT -MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js -LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr -BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB -AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft -Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj -j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH -KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv -2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 -mfnGV/TJVTl4uix5yaaIK/QI +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe +MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v +d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh +cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn +0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ +M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a +MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd +oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI +DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy +oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 +dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy +bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF +BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli +CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE +CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t +3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS +KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== -----END CERTIFICATE----- diff --git a/lib-python/2.7/test/string_tests.py b/lib-python/2.7/test/string_tests.py --- a/lib-python/2.7/test/string_tests.py +++ b/lib-python/2.7/test/string_tests.py @@ -5,6 +5,7 @@ import unittest, string, sys, struct from test import test_support from UserList import UserList +import _testcapi class Sequence: def __init__(self, seq='wxyz'): self.seq = seq @@ -1113,6 +1114,23 @@ self.checkraises(TypeError, '%10.*f', '__mod__', ('foo', 42.)) self.checkraises(ValueError, '%10', '__mod__', (42,)) + width = int(_testcapi.PY_SSIZE_T_MAX + 1) + if width <= sys.maxint: + self.checkraises(OverflowError, '%*s', '__mod__', (width, '')) + prec = int(_testcapi.INT_MAX + 1) + if prec <= sys.maxint: + self.checkraises(OverflowError, '%.*f', '__mod__', (prec, 1. / 7)) + # Issue 15989 + width = int(1 << (_testcapi.PY_SSIZE_T_MAX.bit_length() + 1)) + if width <= sys.maxint: + self.checkraises(OverflowError, '%*s', '__mod__', (width, '')) + prec = int(_testcapi.UINT_MAX + 1) + if prec <= sys.maxint: + self.checkraises(OverflowError, '%.*f', '__mod__', (prec, 1. / 7)) + + class X(object): pass + self.checkraises(TypeError, 'abc', '__mod__', X()) + def test_floatformatting(self): # float formatting for prec in xrange(100): diff --git a/lib-python/2.7/test/subprocessdata/sigchild_ignore.py b/lib-python/2.7/test/subprocessdata/sigchild_ignore.py --- a/lib-python/2.7/test/subprocessdata/sigchild_ignore.py +++ b/lib-python/2.7/test/subprocessdata/sigchild_ignore.py @@ -1,6 +1,15 @@ -import signal, subprocess, sys +import signal, subprocess, sys, time # On Linux this causes os.waitpid to fail with OSError as the OS has already # reaped our child process. The wait() passing the OSError on to the caller # and causing us to exit with an error is what we are testing against. signal.signal(signal.SIGCHLD, signal.SIG_IGN) subprocess.Popen([sys.executable, '-c', 'print("albatross")']).wait() +# Also ensure poll() handles an errno.ECHILD appropriately. +p = subprocess.Popen([sys.executable, '-c', 'print("albatross")']) +num_polls = 0 +while p.poll() is None: + # Waiting for the process to finish. + time.sleep(0.01) # Avoid being a CPU busy loop. + num_polls += 1 + if num_polls > 3000: + raise RuntimeError('poll should have returned 0 within 30 seconds') diff --git a/lib-python/2.7/test/test_StringIO.py b/lib-python/2.7/test/test_StringIO.py --- a/lib-python/2.7/test/test_StringIO.py +++ b/lib-python/2.7/test/test_StringIO.py @@ -5,6 +5,7 @@ import cStringIO import types import array +import sys from test import test_support @@ -27,6 +28,8 @@ eq = self.assertEqual self.assertRaises(TypeError, self._fp.seek) eq(self._fp.read(10), self._line[:10]) + eq(self._fp.read(0), '') + eq(self._fp.readline(0), '') eq(self._fp.readline(), self._line[10:] + '\n') eq(len(self._fp.readlines(60)), 2) self._fp.seek(0) @@ -105,6 +108,45 @@ self._fp.close() self.assertRaises(ValueError, self._fp.getvalue) + @test_support.bigmemtest(test_support._2G + 2**26, memuse=2.001) + def test_reads_from_large_stream(self, size): + linesize = 2**26 # 64 MiB + lines = ['x' * (linesize - 1) + '\n'] * (size // linesize) + \ + ['y' * (size % linesize)] + f = self.MODULE.StringIO(''.join(lines)) + for i, expected in enumerate(lines): + line = f.read(len(expected)) + self.assertEqual(len(line), len(expected)) + self.assertEqual(line, expected) + self.assertEqual(f.read(), '') + f.seek(0) + for i, expected in enumerate(lines): + line = f.readline() + self.assertEqual(len(line), len(expected)) + self.assertEqual(line, expected) + self.assertEqual(f.readline(), '') + f.seek(0) + self.assertEqual(f.readlines(), lines) + self.assertEqual(f.readlines(), []) + f.seek(0) + self.assertEqual(f.readlines(size), lines) + self.assertEqual(f.readlines(), []) + + # In worst case cStringIO requires 2 + 1 + 1/2 + 1/2**2 + ... = 4 + # bytes per input character. + @test_support.bigmemtest(test_support._2G, memuse=4) + def test_writes_to_large_stream(self, size): + s = 'x' * 2**26 # 64 MiB + f = self.MODULE.StringIO() + n = size + while n > len(s): + f.write(s) + n -= len(s) + s = None + f.write('x' * n) + self.assertEqual(len(f.getvalue()), size) + + class TestStringIO(TestGenericStringIO): MODULE = StringIO diff --git a/lib-python/2.7/test/test_aifc.py b/lib-python/2.7/test/test_aifc.py --- a/lib-python/2.7/test/test_aifc.py +++ b/lib-python/2.7/test/test_aifc.py @@ -106,6 +106,13 @@ self.assertEqual(testfile.closed, False) f.close() self.assertEqual(testfile.closed, True) + testfile = open(TESTFN, 'wb') + fout = aifc.open(testfile, 'wb') + self.assertFalse(testfile.closed) + with self.assertRaises(aifc.Error): + fout.close() + self.assertTrue(testfile.closed) + fout.close() # do nothing class AIFCLowLevelTest(unittest.TestCase): diff --git a/lib-python/2.7/test/test_argparse.py b/lib-python/2.7/test/test_argparse.py --- a/lib-python/2.7/test/test_argparse.py +++ b/lib-python/2.7/test/test_argparse.py @@ -1374,6 +1374,7 @@ ('X @hello', NS(a=None, x='X', y=['hello world!'])), ('-a B @recursive Y Z', NS(a='A', x='hello world!', y=['Y', 'Z'])), ('X @recursive Z -a B', NS(a='B', x='X', y=['hello world!', 'Z'])), + (["-a", "", "X", "Y"], NS(a='', x='X', y=['Y'])), ] @@ -1466,6 +1467,22 @@ ('readonly', NS(x=None, spam=RFile('readonly'))), ] +class TestFileTypeDefaults(TempDirMixin, ParserTestCase): + """Test that a file is not created unless the default is needed""" + def setUp(self): + super(TestFileTypeDefaults, self).setUp() + file = open(os.path.join(self.temp_dir, 'good'), 'w') + file.write('good') + file.close() + + argument_signatures = [ + Sig('-c', type=argparse.FileType('r'), default='no-file.txt'), + ] + # should provoke no such file error + failures = [''] + # should not provoke error because default file is created + successes = [('-c good', NS(c=RFile('good')))] + class TestFileTypeRB(TempDirMixin, ParserTestCase): """Test the FileType option/argument type for reading files""" @@ -1763,6 +1780,14 @@ parser2.add_argument('-y', choices='123', help='y help') parser2.add_argument('z', type=complex, nargs='*', help='z help') + # add third sub-parser + parser3_kwargs = dict(description='3 description') + if subparser_help: + parser3_kwargs['help'] = '3 help' + parser3 = subparsers.add_parser('3', **parser3_kwargs) + parser3.add_argument('t', type=int, help='t help') + parser3.add_argument('u', nargs='...', help='u help') + # return the main parser return parser @@ -1792,6 +1817,10 @@ self.parser.parse_args('--foo 0.125 1 c'.split()), NS(foo=True, bar=0.125, w=None, x='c'), ) + self.assertEqual( + self.parser.parse_args('-1.5 3 11 -- a --foo 7 -- b'.split()), + NS(foo=False, bar=-1.5, t=11, u=['a', '--foo', '7', '--', 'b']), + ) def test_parse_known_args(self): self.assertEqual( @@ -1826,15 +1855,15 @@ def test_help(self): self.assertEqual(self.parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + 'usage: PROG [-h] [--foo] bar {1,2,3} ...\n') self.assertEqual(self.parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... + usage: PROG [-h] [--foo] bar {1,2,3} ... main description positional arguments: bar bar help - {1,2} command help + {1,2,3} command help optional arguments: -h, --help show this help message and exit @@ -1845,15 +1874,15 @@ # Make sure - is still used for help if it is a non-first prefix char parser = self._get_parser(prefix_chars='+:-') self.assertEqual(parser.format_usage(), - 'usage: PROG [-h] [++foo] bar {1,2} ...\n') + 'usage: PROG [-h] [++foo] bar {1,2,3} ...\n') self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [++foo] bar {1,2} ... + usage: PROG [-h] [++foo] bar {1,2,3} ... main description positional arguments: bar bar help - {1,2} command help + {1,2,3} command help optional arguments: -h, --help show this help message and exit @@ -1864,15 +1893,15 @@ def test_help_alternate_prefix_chars(self): parser = self._get_parser(prefix_chars='+:/') self.assertEqual(parser.format_usage(), - 'usage: PROG [+h] [++foo] bar {1,2} ...\n') + 'usage: PROG [+h] [++foo] bar {1,2,3} ...\n') self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [+h] [++foo] bar {1,2} ... + usage: PROG [+h] [++foo] bar {1,2,3} ... main description positional arguments: bar bar help - {1,2} command help + {1,2,3} command help optional arguments: +h, ++help show this help message and exit @@ -1881,18 +1910,19 @@ def test_parser_command_help(self): self.assertEqual(self.command_help_parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + 'usage: PROG [-h] [--foo] bar {1,2,3} ...\n') self.assertEqual(self.command_help_parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... + usage: PROG [-h] [--foo] bar {1,2,3} ... main description positional arguments: bar bar help - {1,2} command help + {1,2,3} command help 1 1 help 2 2 help + 3 3 help optional arguments: -h, --help show this help message and exit @@ -4418,12 +4448,95 @@ else: self.fail() +# ================================================ +# Check that the type function is called only once +# ================================================ + +class TestTypeFunctionCallOnlyOnce(TestCase): + + def test_type_function_call_only_once(self): + def spam(string_to_convert): + self.assertEqual(string_to_convert, 'spam!') + return 'foo_converted' + + parser = argparse.ArgumentParser() + parser.add_argument('--foo', type=spam, default='bar') + args = parser.parse_args('--foo spam!'.split()) + self.assertEqual(NS(foo='foo_converted'), args) + +# ================================================================== +# Check semantics regarding the default argument and type conversion +# ================================================================== + +class TestTypeFunctionCalledOnDefault(TestCase): + + def test_type_function_call_with_non_string_default(self): + def spam(int_to_convert): + self.assertEqual(int_to_convert, 0) + return 'foo_converted' + + parser = argparse.ArgumentParser() + parser.add_argument('--foo', type=spam, default=0) + args = parser.parse_args([]) + # foo should *not* be converted because its default is not a string. + self.assertEqual(NS(foo=0), args) + + def test_type_function_call_with_string_default(self): + def spam(int_to_convert): + return 'foo_converted' + + parser = argparse.ArgumentParser() + parser.add_argument('--foo', type=spam, default='0') + args = parser.parse_args([]) + # foo is converted because its default is a string. + self.assertEqual(NS(foo='foo_converted'), args) + + def test_no_double_type_conversion_of_default(self): + def extend(str_to_convert): + return str_to_convert + '*' + + parser = argparse.ArgumentParser() + parser.add_argument('--test', type=extend, default='*') + args = parser.parse_args([]) + # The test argument will be two stars, one coming from the default + # value and one coming from the type conversion being called exactly + # once. + self.assertEqual(NS(test='**'), args) + + def test_issue_15906(self): + # Issue #15906: When action='append', type=str, default=[] are + # providing, the dest value was the string representation "[]" when it + # should have been an empty list. + parser = argparse.ArgumentParser() + parser.add_argument('--test', dest='test', type=str, + default=[], action='append') + args = parser.parse_args([]) + self.assertEqual(args.test, []) + # ====================== # parse_known_args tests # ====================== class TestParseKnownArgs(TestCase): + def test_arguments_tuple(self): + parser = argparse.ArgumentParser() + parser.parse_args(()) + + def test_arguments_list(self): + parser = argparse.ArgumentParser() + parser.parse_args([]) + + def test_arguments_tuple_positional(self): + parser = argparse.ArgumentParser() + parser.add_argument('x') + parser.parse_args(('x',)) + + def test_arguments_list_positional(self): + parser = argparse.ArgumentParser() + parser.add_argument('x') + parser.parse_args(['x']) + def test_optionals(self): parser = argparse.ArgumentParser() parser.add_argument('--foo') diff --git a/lib-python/2.7/test/test_array.py b/lib-python/2.7/test/test_array.py --- a/lib-python/2.7/test/test_array.py +++ b/lib-python/2.7/test/test_array.py @@ -985,6 +985,19 @@ upper = long(pow(2, a.itemsize * 8)) - 1L self.check_overflow(lower, upper) + @test_support.cpython_only + def test_sizeof_with_buffer(self): + a = array.array(self.typecode, self.example) + basesize = test_support.calcvobjsize('4P') + buffer_size = a.buffer_info()[1] * a.itemsize + test_support.check_sizeof(self, a, basesize + buffer_size) + + @test_support.cpython_only + def test_sizeof_without_buffer(self): + a = array.array(self.typecode) + basesize = test_support.calcvobjsize('4P') + test_support.check_sizeof(self, a, basesize) + class ByteTest(SignedNumberTest): typecode = 'b' diff --git a/lib-python/2.7/test/test_ast.py b/lib-python/2.7/test/test_ast.py --- a/lib-python/2.7/test/test_ast.py +++ b/lib-python/2.7/test/test_ast.py @@ -231,6 +231,12 @@ im = ast.parse("from . import y").body[0] self.assertIsNone(im.module) + def test_non_interned_future_from_ast(self): + mod = ast.parse("from __future__ import division") + self.assertIsInstance(mod.body[0], ast.ImportFrom) + mod.body[0].module = " __future__ ".strip() + compile(mod, "", "exec") + def test_base_classes(self): self.assertTrue(issubclass(ast.For, ast.stmt)) self.assertTrue(issubclass(ast.Name, ast.expr)) diff --git a/lib-python/2.7/test/test_asyncore.py b/lib-python/2.7/test/test_asyncore.py --- a/lib-python/2.7/test/test_asyncore.py +++ b/lib-python/2.7/test/test_asyncore.py @@ -7,6 +7,7 @@ import time import warnings import errno +import struct from test import test_support from test.test_support import TESTFN, run_unittest, unlink @@ -483,8 +484,9 @@ return self.socket.getsockname()[:2] def handle_accept(self): - sock, addr = self.accept() - self.handler(sock) + pair = self.accept() + if pair is not None: + self.handler(pair[0]) def handle_error(self): raise @@ -703,6 +705,27 @@ finally: sock.close() + @unittest.skipUnless(threading, 'Threading required for this test.') + @test_support.reap_threads + def test_quick_connect(self): + # see: http://bugs.python.org/issue10340 + server = TCPServer() + t = threading.Thread(target=lambda: asyncore.loop(timeout=0.1, count=500)) + t.start() + self.addCleanup(t.join) + + for x in xrange(20): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(.2) + s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, + struct.pack('ii', 1, 0)) + try: + s.connect(server.address) + except socket.error: + pass + finally: + s.close() + class TestAPI_UseSelect(BaseTestAPI): use_poll = False diff --git a/lib-python/2.7/test/test_audioop.py b/lib-python/2.7/test/test_audioop.py --- a/lib-python/2.7/test/test_audioop.py +++ b/lib-python/2.7/test/test_audioop.py @@ -1,25 +1,33 @@ import audioop +import sys import unittest +import struct from test.test_support import run_unittest -endian = 'big' if audioop.getsample('\0\1', 2, 0) == 1 else 'little' -def gendata1(): - return '\0\1\2' +formats = { + 1: 'b', + 2: 'h', + 4: 'i', +} -def gendata2(): - if endian == 'big': - return '\0\0\0\1\0\2' - else: - return '\0\0\1\0\2\0' +def pack(width, data): + return struct.pack('=%d%s' % (len(data), formats[width]), *data) -def gendata4(): - if endian == 'big': - return '\0\0\0\0\0\0\0\1\0\0\0\2' - else: - return '\0\0\0\0\1\0\0\0\2\0\0\0' +packs = { + 1: lambda *data: pack(1, data), + 2: lambda *data: pack(2, data), + 4: lambda *data: pack(4, data), +} +maxvalues = {w: (1 << (8 * w - 1)) - 1 for w in (1, 2, 4)} +minvalues = {w: -1 << (8 * w - 1) for w in (1, 2, 4)} -data = [gendata1(), gendata2(), gendata4()] +datas = { + 1: b'\x00\x12\x45\xbb\x7f\x80\xff', + 2: packs[2](0, 0x1234, 0x4567, -0x4567, 0x7fff, -0x8000, -1), + 4: packs[4](0, 0x12345678, 0x456789ab, -0x456789ab, + 0x7fffffff, -0x80000000, -1), +} INVALID_DATA = [ (b'abc', 0), @@ -31,164 +39,315 @@ class TestAudioop(unittest.TestCase): def test_max(self): - self.assertEqual(audioop.max(data[0], 1), 2) - self.assertEqual(audioop.max(data[1], 2), 2) - self.assertEqual(audioop.max(data[2], 4), 2) + for w in 1, 2, 4: + self.assertEqual(audioop.max(b'', w), 0) + p = packs[w] + self.assertEqual(audioop.max(p(5), w), 5) + self.assertEqual(audioop.max(p(5, -8, -1), w), 8) + self.assertEqual(audioop.max(p(maxvalues[w]), w), maxvalues[w]) + self.assertEqual(audioop.max(p(minvalues[w]), w), -minvalues[w]) + self.assertEqual(audioop.max(datas[w], w), -minvalues[w]) def test_minmax(self): - self.assertEqual(audioop.minmax(data[0], 1), (0, 2)) - self.assertEqual(audioop.minmax(data[1], 2), (0, 2)) - self.assertEqual(audioop.minmax(data[2], 4), (0, 2)) + for w in 1, 2, 4: + self.assertEqual(audioop.minmax(b'', w), + (0x7fffffff, -0x80000000)) + p = packs[w] + self.assertEqual(audioop.minmax(p(5), w), (5, 5)) + self.assertEqual(audioop.minmax(p(5, -8, -1), w), (-8, 5)) + self.assertEqual(audioop.minmax(p(maxvalues[w]), w), + (maxvalues[w], maxvalues[w])) + self.assertEqual(audioop.minmax(p(minvalues[w]), w), + (minvalues[w], minvalues[w])) + self.assertEqual(audioop.minmax(datas[w], w), + (minvalues[w], maxvalues[w])) def test_maxpp(self): - self.assertEqual(audioop.maxpp(data[0], 1), 0) - self.assertEqual(audioop.maxpp(data[1], 2), 0) - self.assertEqual(audioop.maxpp(data[2], 4), 0) + for w in 1, 2, 4: + self.assertEqual(audioop.maxpp(b'', w), 0) + self.assertEqual(audioop.maxpp(packs[w](*range(100)), w), 0) + self.assertEqual(audioop.maxpp(packs[w](9, 10, 5, 5, 0, 1), w), 10) + self.assertEqual(audioop.maxpp(datas[w], w), + maxvalues[w] - minvalues[w]) def test_avg(self): - self.assertEqual(audioop.avg(data[0], 1), 1) - self.assertEqual(audioop.avg(data[1], 2), 1) - self.assertEqual(audioop.avg(data[2], 4), 1) + for w in 1, 2, 4: + self.assertEqual(audioop.avg(b'', w), 0) + p = packs[w] + self.assertEqual(audioop.avg(p(5), w), 5) + self .assertEqual(audioop.avg(p(5, 8), w), 6) + self.assertEqual(audioop.avg(p(5, -8), w), -2) + self.assertEqual(audioop.avg(p(maxvalues[w], maxvalues[w]), w), + maxvalues[w]) + self.assertEqual(audioop.avg(p(minvalues[w], minvalues[w]), w), + minvalues[w]) + self.assertEqual(audioop.avg(packs[4](0x50000000, 0x70000000), 4), + 0x60000000) + self.assertEqual(audioop.avg(packs[4](-0x50000000, -0x70000000), 4), + -0x60000000) def test_avgpp(self): - self.assertEqual(audioop.avgpp(data[0], 1), 0) - self.assertEqual(audioop.avgpp(data[1], 2), 0) - self.assertEqual(audioop.avgpp(data[2], 4), 0) + for w in 1, 2, 4: + self.assertEqual(audioop.avgpp(b'', w), 0) + self.assertEqual(audioop.avgpp(packs[w](*range(100)), w), 0) + self.assertEqual(audioop.avgpp(packs[w](9, 10, 5, 5, 0, 1), w), 10) + self.assertEqual(audioop.avgpp(datas[1], 1), 196) + self.assertEqual(audioop.avgpp(datas[2], 2), 50534) + self.assertEqual(audioop.avgpp(datas[4], 4), 3311897002) def test_rms(self): - self.assertEqual(audioop.rms(data[0], 1), 1) - self.assertEqual(audioop.rms(data[1], 2), 1) - self.assertEqual(audioop.rms(data[2], 4), 1) + for w in 1, 2, 4: + self.assertEqual(audioop.rms(b'', w), 0) + p = packs[w] + self.assertEqual(audioop.rms(p(*range(100)), w), 57) + self.assertAlmostEqual(audioop.rms(p(maxvalues[w]) * 5, w), + maxvalues[w], delta=1) + self.assertAlmostEqual(audioop.rms(p(minvalues[w]) * 5, w), + -minvalues[w], delta=1) + self.assertEqual(audioop.rms(datas[1], 1), 77) + self.assertEqual(audioop.rms(datas[2], 2), 20001) + self.assertEqual(audioop.rms(datas[4], 4), 1310854152) def test_cross(self): - self.assertEqual(audioop.cross(data[0], 1), 0) - self.assertEqual(audioop.cross(data[1], 2), 0) - self.assertEqual(audioop.cross(data[2], 4), 0) + for w in 1, 2, 4: + self.assertEqual(audioop.cross(b'', w), -1) + p = packs[w] + self.assertEqual(audioop.cross(p(0, 1, 2), w), 0) + self.assertEqual(audioop.cross(p(1, 2, -3, -4), w), 1) + self.assertEqual(audioop.cross(p(-1, -2, 3, 4), w), 1) + self.assertEqual(audioop.cross(p(0, minvalues[w]), w), 1) + self.assertEqual(audioop.cross(p(minvalues[w], maxvalues[w]), w), 1) def test_add(self): - data2 = [] - for d in data: - str = '' - for s in d: - str = str + chr(ord(s)*2) - data2.append(str) - self.assertEqual(audioop.add(data[0], data[0], 1), data2[0]) - self.assertEqual(audioop.add(data[1], data[1], 2), data2[1]) - self.assertEqual(audioop.add(data[2], data[2], 4), data2[2]) + for w in 1, 2, 4: + self.assertEqual(audioop.add(b'', b'', w), b'') + self.assertEqual(audioop.add(datas[w], b'\0' * len(datas[w]), w), + datas[w]) + self.assertEqual(audioop.add(datas[1], datas[1], 1), + b'\x00\x24\x7f\x80\x7f\x80\xfe') + self.assertEqual(audioop.add(datas[2], datas[2], 2), + packs[2](0, 0x2468, 0x7fff, -0x8000, 0x7fff, -0x8000, -2)) + self.assertEqual(audioop.add(datas[4], datas[4], 4), + packs[4](0, 0x2468acf0, 0x7fffffff, -0x80000000, + 0x7fffffff, -0x80000000, -2)) def test_bias(self): - # Note: this test assumes that avg() works - d1 = audioop.bias(data[0], 1, 100) - d2 = audioop.bias(data[1], 2, 100) - d4 = audioop.bias(data[2], 4, 100) - self.assertEqual(audioop.avg(d1, 1), 101) - self.assertEqual(audioop.avg(d2, 2), 101) - self.assertEqual(audioop.avg(d4, 4), 101) + for w in 1, 2, 4: + for bias in 0, 1, -1, 127, -128, 0x7fffffff, -0x80000000: + self.assertEqual(audioop.bias(b'', w, bias), b'') + self.assertEqual(audioop.bias(datas[1], 1, 1), + b'\x01\x13\x46\xbc\x80\x81\x00') + self.assertEqual(audioop.bias(datas[1], 1, -1), + b'\xff\x11\x44\xba\x7e\x7f\xfe') + self.assertEqual(audioop.bias(datas[1], 1, 0x7fffffff), + b'\xff\x11\x44\xba\x7e\x7f\xfe') + self.assertEqual(audioop.bias(datas[1], 1, -0x80000000), + datas[1]) + self.assertEqual(audioop.bias(datas[2], 2, 1), + packs[2](1, 0x1235, 0x4568, -0x4566, -0x8000, -0x7fff, 0)) + self.assertEqual(audioop.bias(datas[2], 2, -1), + packs[2](-1, 0x1233, 0x4566, -0x4568, 0x7ffe, 0x7fff, -2)) + self.assertEqual(audioop.bias(datas[2], 2, 0x7fffffff), + packs[2](-1, 0x1233, 0x4566, -0x4568, 0x7ffe, 0x7fff, -2)) + self.assertEqual(audioop.bias(datas[2], 2, -0x80000000), + datas[2]) + self.assertEqual(audioop.bias(datas[4], 4, 1), + packs[4](1, 0x12345679, 0x456789ac, -0x456789aa, + -0x80000000, -0x7fffffff, 0)) + self.assertEqual(audioop.bias(datas[4], 4, -1), + packs[4](-1, 0x12345677, 0x456789aa, -0x456789ac, + 0x7ffffffe, 0x7fffffff, -2)) + self.assertEqual(audioop.bias(datas[4], 4, 0x7fffffff), + packs[4](0x7fffffff, -0x6dcba989, -0x3a987656, 0x3a987654, + -2, -1, 0x7ffffffe)) + self.assertEqual(audioop.bias(datas[4], 4, -0x80000000), + packs[4](-0x80000000, -0x6dcba988, -0x3a987655, 0x3a987655, + -1, 0, 0x7fffffff)) def test_lin2lin(self): - # too simple: we test only the size - for d1 in data: - for d2 in data: - got = len(d1)//3 - wtd = len(d2)//3 - self.assertEqual(len(audioop.lin2lin(d1, got, wtd)), len(d2)) + for w in 1, 2, 4: + self.assertEqual(audioop.lin2lin(datas[w], w, w), datas[w]) + + self.assertEqual(audioop.lin2lin(datas[1], 1, 2), + packs[2](0, 0x1200, 0x4500, -0x4500, 0x7f00, -0x8000, -0x100)) + self.assertEqual(audioop.lin2lin(datas[1], 1, 4), + packs[4](0, 0x12000000, 0x45000000, -0x45000000, + 0x7f000000, -0x80000000, -0x1000000)) + self.assertEqual(audioop.lin2lin(datas[2], 2, 1), + b'\x00\x12\x45\xba\x7f\x80\xff') + self.assertEqual(audioop.lin2lin(datas[2], 2, 4), + packs[4](0, 0x12340000, 0x45670000, -0x45670000, + 0x7fff0000, -0x80000000, -0x10000)) + self.assertEqual(audioop.lin2lin(datas[4], 4, 1), + b'\x00\x12\x45\xba\x7f\x80\xff') + self.assertEqual(audioop.lin2lin(datas[4], 4, 2), + packs[2](0, 0x1234, 0x4567, -0x4568, 0x7fff, -0x8000, -1)) def test_adpcm2lin(self): + self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 1, None), + (b'\x00\x00\x00\xff\x00\xff', (-179, 40))) + self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 2, None), + (packs[2](0, 0xb, 0x29, -0x16, 0x72, -0xb3), (-179, 40))) + self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 4, None), + (packs[4](0, 0xb0000, 0x290000, -0x160000, 0x720000, + -0xb30000), (-179, 40))) + # Very cursory test - self.assertEqual(audioop.adpcm2lin(b'\0\0', 1, None), (b'\0' * 4, (0,0))) - self.assertEqual(audioop.adpcm2lin(b'\0\0', 2, None), (b'\0' * 8, (0,0))) - self.assertEqual(audioop.adpcm2lin(b'\0\0', 4, None), (b'\0' * 16, (0,0))) + for w in 1, 2, 4: + self.assertEqual(audioop.adpcm2lin(b'\0' * 5, w, None), + (b'\0' * w * 10, (0, 0))) def test_lin2adpcm(self): + self.assertEqual(audioop.lin2adpcm(datas[1], 1, None), + (b'\x07\x7f\x7f', (-221, 39))) + self.assertEqual(audioop.lin2adpcm(datas[2], 2, None), + (b'\x07\x7f\x7f', (31, 39))) + self.assertEqual(audioop.lin2adpcm(datas[4], 4, None), + (b'\x07\x7f\x7f', (31, 39))) + # Very cursory test - self.assertEqual(audioop.lin2adpcm('\0\0\0\0', 1, None), ('\0\0', (0,0))) + for w in 1, 2, 4: + self.assertEqual(audioop.lin2adpcm(b'\0' * w * 10, w, None), + (b'\0' * 5, (0, 0))) def test_lin2alaw(self): - self.assertEqual(audioop.lin2alaw(data[0], 1), '\xd5\xc5\xf5') - self.assertEqual(audioop.lin2alaw(data[1], 2), '\xd5\xd5\xd5') - self.assertEqual(audioop.lin2alaw(data[2], 4), '\xd5\xd5\xd5') + self.assertEqual(audioop.lin2alaw(datas[1], 1), + b'\xd5\x87\xa4\x24\xaa\x2a\x5a') + self.assertEqual(audioop.lin2alaw(datas[2], 2), + b'\xd5\x87\xa4\x24\xaa\x2a\x55') + self.assertEqual(audioop.lin2alaw(datas[4], 4), + b'\xd5\x87\xa4\x24\xaa\x2a\x55') def test_alaw2lin(self): - # Cursory - d = audioop.lin2alaw(data[0], 1) - self.assertEqual(audioop.alaw2lin(d, 1), data[0]) - if endian == 'big': - self.assertEqual(audioop.alaw2lin(d, 2), - b'\x00\x08\x01\x08\x02\x10') - self.assertEqual(audioop.alaw2lin(d, 4), - b'\x00\x08\x00\x00\x01\x08\x00\x00\x02\x10\x00\x00') - else: - self.assertEqual(audioop.alaw2lin(d, 2), - b'\x08\x00\x08\x01\x10\x02') - self.assertEqual(audioop.alaw2lin(d, 4), - b'\x00\x00\x08\x00\x00\x00\x08\x01\x00\x00\x10\x02') + encoded = b'\x00\x03\x24\x2a\x51\x54\x55\x58\x6b\x71\x7f'\ + b'\x80\x83\xa4\xaa\xd1\xd4\xd5\xd8\xeb\xf1\xff' + src = [-688, -720, -2240, -4032, -9, -3, -1, -27, -244, -82, -106, + 688, 720, 2240, 4032, 9, 3, 1, 27, 244, 82, 106] + for w in 1, 2, 4: + self.assertEqual(audioop.alaw2lin(encoded, w), + packs[w](*(x << (w * 8) >> 13 for x in src))) + + encoded = ''.join(chr(x) for x in xrange(256)) + for w in 2, 4: + decoded = audioop.alaw2lin(encoded, w) + self.assertEqual(audioop.lin2alaw(decoded, w), encoded) def test_lin2ulaw(self): - self.assertEqual(audioop.lin2ulaw(data[0], 1), '\xff\xe7\xdb') - self.assertEqual(audioop.lin2ulaw(data[1], 2), '\xff\xff\xff') - self.assertEqual(audioop.lin2ulaw(data[2], 4), '\xff\xff\xff') + self.assertEqual(audioop.lin2ulaw(datas[1], 1), + b'\xff\xad\x8e\x0e\x80\x00\x67') + self.assertEqual(audioop.lin2ulaw(datas[2], 2), + b'\xff\xad\x8e\x0e\x80\x00\x7e') + self.assertEqual(audioop.lin2ulaw(datas[4], 4), + b'\xff\xad\x8e\x0e\x80\x00\x7e') def test_ulaw2lin(self): - # Cursory - d = audioop.lin2ulaw(data[0], 1) - self.assertEqual(audioop.ulaw2lin(d, 1), data[0]) - if endian == 'big': - self.assertEqual(audioop.ulaw2lin(d, 2), - b'\x00\x00\x01\x04\x02\x0c') - self.assertEqual(audioop.ulaw2lin(d, 4), - b'\x00\x00\x00\x00\x01\x04\x00\x00\x02\x0c\x00\x00') - else: - self.assertEqual(audioop.ulaw2lin(d, 2), - b'\x00\x00\x04\x01\x0c\x02') - self.assertEqual(audioop.ulaw2lin(d, 4), - b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x0c\x02') + encoded = b'\x00\x0e\x28\x3f\x57\x6a\x76\x7c\x7e\x7f'\ + b'\x80\x8e\xa8\xbf\xd7\xea\xf6\xfc\xfe\xff' + src = [-8031, -4447, -1471, -495, -163, -53, -18, -6, -2, 0, + 8031, 4447, 1471, 495, 163, 53, 18, 6, 2, 0] + for w in 1, 2, 4: + self.assertEqual(audioop.ulaw2lin(encoded, w), + packs[w](*(x << (w * 8) >> 14 for x in src))) + + # Current u-law implementation has two codes fo 0: 0x7f and 0xff. + encoded = ''.join(chr(x) for x in range(127) + range(128, 256)) + for w in 2, 4: + decoded = audioop.ulaw2lin(encoded, w) + self.assertEqual(audioop.lin2ulaw(decoded, w), encoded) def test_mul(self): - data2 = [] - for d in data: - str = '' - for s in d: - str = str + chr(ord(s)*2) - data2.append(str) - self.assertEqual(audioop.mul(data[0], 1, 2), data2[0]) - self.assertEqual(audioop.mul(data[1],2, 2), data2[1]) - self.assertEqual(audioop.mul(data[2], 4, 2), data2[2]) + for w in 1, 2, 4: + self.assertEqual(audioop.mul(b'', w, 2), b'') + self.assertEqual(audioop.mul(datas[w], w, 0), + b'\0' * len(datas[w])) + self.assertEqual(audioop.mul(datas[w], w, 1), + datas[w]) + self.assertEqual(audioop.mul(datas[1], 1, 2), + b'\x00\x24\x7f\x80\x7f\x80\xfe') + self.assertEqual(audioop.mul(datas[2], 2, 2), + packs[2](0, 0x2468, 0x7fff, -0x8000, 0x7fff, -0x8000, -2)) + self.assertEqual(audioop.mul(datas[4], 4, 2), + packs[4](0, 0x2468acf0, 0x7fffffff, -0x80000000, + 0x7fffffff, -0x80000000, -2)) def test_ratecv(self): + for w in 1, 2, 4: + self.assertEqual(audioop.ratecv(b'', w, 1, 8000, 8000, None), + (b'', (-1, ((0, 0),)))) + self.assertEqual(audioop.ratecv(b'', w, 5, 8000, 8000, None), + (b'', (-1, ((0, 0),) * 5))) + self.assertEqual(audioop.ratecv(b'', w, 1, 8000, 16000, None), + (b'', (-2, ((0, 0),)))) + self.assertEqual(audioop.ratecv(datas[w], w, 1, 8000, 8000, None)[0], + datas[w]) state = None - d1, state = audioop.ratecv(data[0], 1, 1, 8000, 16000, state) - d2, state = audioop.ratecv(data[0], 1, 1, 8000, 16000, state) - self.assertEqual(d1 + d2, '\000\000\001\001\002\001\000\000\001\001\002') + d1, state = audioop.ratecv(b'\x00\x01\x02', 1, 1, 8000, 16000, state) + d2, state = audioop.ratecv(b'\x00\x01\x02', 1, 1, 8000, 16000, state) + self.assertEqual(d1 + d2, b'\000\000\001\001\002\001\000\000\001\001\002') + + for w in 1, 2, 4: + d0, state0 = audioop.ratecv(datas[w], w, 1, 8000, 16000, None) + d, state = b'', None + for i in range(0, len(datas[w]), w): + d1, state = audioop.ratecv(datas[w][i:i + w], w, 1, + 8000, 16000, state) + d += d1 + self.assertEqual(d, d0) + self.assertEqual(state, state0) def test_reverse(self): - self.assertEqual(audioop.reverse(data[0], 1), '\2\1\0') + for w in 1, 2, 4: + self.assertEqual(audioop.reverse(b'', w), b'') + self.assertEqual(audioop.reverse(packs[w](0, 1, 2), w), + packs[w](2, 1, 0)) def test_tomono(self): - data2 = '' - for d in data[0]: - data2 = data2 + d + d - self.assertEqual(audioop.tomono(data2, 1, 0.5, 0.5), data[0]) + for w in 1, 2, 4: + data1 = datas[w] + data2 = bytearray(2 * len(data1)) + for k in range(w): + data2[k::2*w] = data1[k::w] + self.assertEqual(audioop.tomono(str(data2), w, 1, 0), data1) + self.assertEqual(audioop.tomono(str(data2), w, 0, 1), b'\0' * len(data1)) + for k in range(w): + data2[k+w::2*w] = data1[k::w] + self.assertEqual(audioop.tomono(str(data2), w, 0.5, 0.5), data1) def test_tostereo(self): - data2 = '' - for d in data[0]: - data2 = data2 + d + d - self.assertEqual(audioop.tostereo(data[0], 1, 1, 1), data2) + for w in 1, 2, 4: + data1 = datas[w] + data2 = bytearray(2 * len(data1)) + for k in range(w): + data2[k::2*w] = data1[k::w] + self.assertEqual(audioop.tostereo(data1, w, 1, 0), data2) + self.assertEqual(audioop.tostereo(data1, w, 0, 0), b'\0' * len(data2)) + for k in range(w): + data2[k+w::2*w] = data1[k::w] + self.assertEqual(audioop.tostereo(data1, w, 1, 1), data2) def test_findfactor(self): - self.assertEqual(audioop.findfactor(data[1], data[1]), 1.0) + self.assertEqual(audioop.findfactor(datas[2], datas[2]), 1.0) + self.assertEqual(audioop.findfactor(b'\0' * len(datas[2]), datas[2]), + 0.0) def test_findfit(self): - self.assertEqual(audioop.findfit(data[1], data[1]), (0, 1.0)) + self.assertEqual(audioop.findfit(datas[2], datas[2]), (0, 1.0)) + self.assertEqual(audioop.findfit(datas[2], packs[2](1, 2, 0)), + (1, 8038.8)) + self.assertEqual(audioop.findfit(datas[2][:-2] * 5 + datas[2], datas[2]), + (30, 1.0)) def test_findmax(self): - self.assertEqual(audioop.findmax(data[1], 1), 2) + self.assertEqual(audioop.findmax(datas[2], 1), 5) def test_getsample(self): - for i in range(3): - self.assertEqual(audioop.getsample(data[0], 1, i), i) - self.assertEqual(audioop.getsample(data[1], 2, i), i) - self.assertEqual(audioop.getsample(data[2], 4, i), i) + for w in 1, 2, 4: + data = packs[w](0, 1, -1, maxvalues[w], minvalues[w]) + self.assertEqual(audioop.getsample(data, w, 0), 0) + self.assertEqual(audioop.getsample(data, w, 1), 1) + self.assertEqual(audioop.getsample(data, w, 2), -1) + self.assertEqual(audioop.getsample(data, w, 3), maxvalues[w]) + self.assertEqual(audioop.getsample(data, w, 4), minvalues[w]) def test_negativelen(self): # from issue 3306, previously it segfaulted @@ -220,9 +379,9 @@ self.assertRaises(audioop.error, audioop.lin2adpcm, data, size, state) def test_wrongsize(self): - data = b'abc' + data = b'abcdefgh' state = None - for size in (-1, 3, 5): + for size in (-1, 0, 3, 5, 1024): self.assertRaises(audioop.error, audioop.ulaw2lin, data, size) self.assertRaises(audioop.error, audioop.alaw2lin, data, size) self.assertRaises(audioop.error, audioop.adpcm2lin, data, size, state) diff --git a/lib-python/2.7/test/test_bigmem.py b/lib-python/2.7/test/test_bigmem.py --- a/lib-python/2.7/test/test_bigmem.py +++ b/lib-python/2.7/test/test_bigmem.py @@ -118,12 +118,13 @@ except MemoryError: pass # acceptable on 32-bit - @precisionbigmemtest(size=_2G-1, memuse=2) + @precisionbigmemtest(size=_2G-1, memuse=4) def test_decodeascii(self, size): return self.basic_encode_test(size, 'ascii', c='A') @precisionbigmemtest(size=_4G // 5, memuse=6+2) def test_unicode_repr_oflw(self, size): + self.skipTest("test crashes - see issue #14904") try: s = u"\uAAAA"*size r = repr(s) @@ -485,7 +486,7 @@ self.assertEqual(s.count('.'), 3) self.assertEqual(s.count('-'), size * 2) - @bigmemtest(minsize=_2G + 10, memuse=2) + @bigmemtest(minsize=_2G + 10, memuse=5) def test_repr_small(self, size): s = '-' * size s = repr(s) @@ -497,7 +498,6 @@ # repr() will create a string four times as large as this 'binary # string', but we don't want to allocate much more than twice # size in total. (We do extra testing in test_repr_large()) - size = size // 5 * 2 s = '\x00' * size s = repr(s) self.assertEqual(len(s), size * 4 + 2) @@ -541,7 +541,7 @@ self.assertEqual(len(s), size * 2) self.assertEqual(s.count('.'), size * 2) - @bigmemtest(minsize=_2G + 20, memuse=1) + @bigmemtest(minsize=_2G + 20, memuse=2) def test_slice_and_getitem(self, size): SUBSTR = '0123456789' sublen = len(SUBSTR) diff --git a/lib-python/2.7/test/test_bisect.py b/lib-python/2.7/test/test_bisect.py --- a/lib-python/2.7/test/test_bisect.py +++ b/lib-python/2.7/test/test_bisect.py @@ -23,6 +23,28 @@ import bisect as c_bisect +class Range(object): + """A trivial xrange()-like object without any integer width limitations.""" + def __init__(self, start, stop): + self.start = start + self.stop = stop + self.last_insert = None + + def __len__(self): + return self.stop - self.start + + def __getitem__(self, idx): + n = self.stop - self.start + if idx < 0: + idx += n + if idx >= n: + raise IndexError(idx) + return self.start + idx + + def insert(self, idx, item): + self.last_insert = idx, item + + class TestBisect(unittest.TestCase): module = None @@ -122,6 +144,35 @@ self.assertRaises(ValueError, mod.insort_left, [1, 2, 3], 5, -1, 3), self.assertRaises(ValueError, mod.insort_right, [1, 2, 3], 5, -1, 3), + def test_large_range(self): + # Issue 13496 + mod = self.module + n = sys.maxsize + try: + data = xrange(n-1) + except OverflowError: + self.skipTest("can't create a xrange() object of size `sys.maxsize`") + self.assertEqual(mod.bisect_left(data, n-3), n-3) + self.assertEqual(mod.bisect_right(data, n-3), n-2) + self.assertEqual(mod.bisect_left(data, n-3, n-10, n), n-3) + self.assertEqual(mod.bisect_right(data, n-3, n-10, n), n-2) + + def test_large_pyrange(self): + # Same as above, but without C-imposed limits on range() parameters + mod = self.module + n = sys.maxsize + data = Range(0, n-1) + self.assertEqual(mod.bisect_left(data, n-3), n-3) + self.assertEqual(mod.bisect_right(data, n-3), n-2) + self.assertEqual(mod.bisect_left(data, n-3, n-10, n), n-3) + self.assertEqual(mod.bisect_right(data, n-3, n-10, n), n-2) + x = n - 100 + mod.insort_left(data, x, x - 50, x + 50) + self.assertEqual(data.last_insert, (x, x)) + x = n - 200 + mod.insort_right(data, x, x - 50, x + 50) + self.assertEqual(data.last_insert, (x + 1, x)) + def test_random(self, n=25): from random import randrange for i in xrange(n): @@ -191,7 +242,7 @@ else: f = self.module.insort_right f(insorted, digit) - self.assertEqual(sorted(insorted), insorted) + self.assertEqual(sorted(insorted), insorted) def test_backcompatibility(self): self.assertEqual(self.module.insort, self.module.insort_right) diff --git a/lib-python/2.7/test/test_builtin.py b/lib-python/2.7/test/test_builtin.py --- a/lib-python/2.7/test/test_builtin.py +++ b/lib-python/2.7/test/test_builtin.py @@ -110,6 +110,7 @@ self.assertRaises(TypeError, all) # No args self.assertRaises(TypeError, all, [2, 4, 6], []) # Too many args self.assertEqual(all([]), True) # Empty iterator + self.assertEqual(all([0, TestFailingBool()]), False)# Short-circuit S = [50, 60] self.assertEqual(all(x > 42 for x in S), True) S = [50, 40, 60] @@ -119,11 +120,12 @@ self.assertEqual(any([None, None, None]), False) self.assertEqual(any([None, 4, None]), True) self.assertRaises(RuntimeError, any, [None, TestFailingBool(), 6]) - self.assertRaises(RuntimeError, all, TestFailingIter()) + self.assertRaises(RuntimeError, any, TestFailingIter()) self.assertRaises(TypeError, any, 10) # Non-iterable self.assertRaises(TypeError, any) # No args self.assertRaises(TypeError, any, [2, 4, 6], []) # Too many args self.assertEqual(any([]), False) # Empty iterator + self.assertEqual(any([1, TestFailingBool()]), True) # Short-circuit S = [40, 60, 30] self.assertEqual(any(x > 42 for x in S), True) S = [10, 20, 30] @@ -680,6 +682,8 @@ # Test input() later, together with raw_input + # test_int(): see test_int.py for int() tests. + def test_intern(self): self.assertRaises(TypeError, intern) # This fails if the test is run twice with a constant string, diff --git a/lib-python/2.7/test/test_bytes.py b/lib-python/2.7/test/test_bytes.py --- a/lib-python/2.7/test/test_bytes.py +++ b/lib-python/2.7/test/test_bytes.py @@ -635,6 +635,26 @@ b[3:0] = [42, 42, 42] self.assertEqual(b, bytearray([0, 1, 2, 42, 42, 42, 3, 4, 5, 6, 7, 8, 9])) + b[3:] = b'foo' + self.assertEqual(b, bytearray([0, 1, 2, 102, 111, 111])) + + b[:3] = memoryview(b'foo') + self.assertEqual(b, bytearray([102, 111, 111, 102, 111, 111])) + + b[3:4] = [] + self.assertEqual(b, bytearray([102, 111, 111, 111, 111])) + + b[1:] = list(b'uuuu') # this works only on Python2 + self.assertEqual(b, bytearray([102, 117, 117, 117, 117])) + + for elem in [5, -5, 0, long(10e20), u'str', 2.3, [u'a', u'b'], [[]]]: + with self.assertRaises(TypeError): + b[3:4] = elem + + for elem in [[254, 255, 256], [-256, 9000]]: + with self.assertRaises(ValueError): + b[3:4] = elem + def test_extended_set_del_slice(self): indices = (0, None, 1, 3, 19, 300, 1<<333, -1, -2, -31, -300) for start in indices: @@ -905,6 +925,7 @@ self.assertEqual(bytes(b"abc") < b"ab", False) self.assertEqual(bytes(b"abc") <= b"ab", False) + @test.test_support.requires_docstrings def test_doc(self): self.assertIsNotNone(bytearray.__doc__) self.assertTrue(bytearray.__doc__.startswith("bytearray("), bytearray.__doc__) diff --git a/lib-python/2.7/test/test_bz2.py b/lib-python/2.7/test/test_bz2.py --- a/lib-python/2.7/test/test_bz2.py +++ b/lib-python/2.7/test/test_bz2.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from test import test_support -from test.test_support import TESTFN, import_module +from test.test_support import TESTFN, _4G, bigmemtest, import_module, findfile import unittest from cStringIO import StringIO @@ -23,6 +23,10 @@ TEXT = 'root:x:0:0:root:/root:/bin/bash\nbin:x:1:1:bin:/bin:\ndaemon:x:2:2:daemon:/sbin:\nadm:x:3:4:adm:/var/adm:\nlp:x:4:7:lp:/var/spool/lpd:\nsync:x:5:0:sync:/sbin:/bin/sync\nshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\nhalt:x:7:0:halt:/sbin:/sbin/halt\nmail:x:8:12:mail:/var/spool/mail:\nnews:x:9:13:news:/var/spool/news:\nuucp:x:10:14:uucp:/var/spool/uucp:\noperator:x:11:0:operator:/root:\ngames:x:12:100:games:/usr/games:\ngopher:x:13:30:gopher:/usr/lib/gopher-data:\nftp:x:14:50:FTP User:/var/ftp:/bin/bash\nnobody:x:65534:65534:Nobody:/home:\npostfix:x:100:101:postfix:/var/spool/postfix:\nniemeyer:x:500:500::/home/niemeyer:/bin/bash\npostgres:x:101:102:PostgreSQL Server:/var/lib/pgsql:/bin/bash\nmysql:x:102:103:MySQL server:/var/lib/mysql:/bin/bash\nwww:x:103:104::/var/www:/bin/false\n' DATA = 'BZh91AY&SY.\xc8N\x18\x00\x01>_\x80\x00\x10@\x02\xff\xf0\x01\x07n\x00?\xe7\xff\xe00\x01\x99\xaa\x00\xc0\x03F\x86\x8c#&\x83F\x9a\x03\x06\xa6\xd0\xa6\x93M\x0fQ\xa7\xa8\x06\x804hh\x12$\x11\xa4i4\xf14S\xd2\x88\xe5\xcd9gd6\x0b\n\xe9\x9b\xd5\x8a\x99\xf7\x08.K\x8ev\xfb\xf7xw\xbb\xdf\xa1\x92\xf1\xdd|/";\xa2\xba\x9f\xd5\xb1#A\xb6\xf6\xb3o\xc9\xc5y\\\xebO\xe7\x85\x9a\xbc\xb6f8\x952\xd5\xd7"%\x89>V,\xf7\xa6z\xe2\x9f\xa3\xdf\x11\x11"\xd6E)I\xa9\x13^\xca\xf3r\xd0\x03U\x922\xf26\xec\xb6\xed\x8b\xc3U\x13\x9d\xc5\x170\xa4\xfa^\x92\xacDF\x8a\x97\xd6\x19\xfe\xdd\xb8\xbd\x1a\x9a\x19\xa3\x80ankR\x8b\xe5\xd83]\xa9\xc6\x08\x82f\xf6\xb9"6l$\xb8j@\xc0\x8a\xb0l1..\xbak\x83ls\x15\xbc\xf4\xc1\x13\xbe\xf8E\xb8\x9d\r\xa8\x9dk\x84\xd3n\xfa\xacQ\x07\xb1%y\xaav\xb4\x08\xe0z\x1b\x16\xf5\x04\xe9\xcc\xb9\x08z\x1en7.G\xfc]\xc9\x14\xe1B@\xbb!8`' DATA_CRLF = 'BZh91AY&SY\xaez\xbbN\x00\x01H\xdf\x80\x00\x12@\x02\xff\xf0\x01\x07n\x00?\xe7\xff\xe0@\x01\xbc\xc6`\x86*\x8d=M\xa9\x9a\x86\xd0L@\x0fI\xa6!\xa1\x13\xc8\x88jdi\x8d@\x03@\x1a\x1a\x0c\x0c\x83 \x00\xc4h2\x19\x01\x82D\x84e\t\xe8\x99\x89\x19\x1ah\x00\r\x1a\x11\xaf\x9b\x0fG\xf5(\x1b\x1f?\t\x12\xcf\xb5\xfc\x95E\x00ps\x89\x12^\xa4\xdd\xa2&\x05(\x87\x04\x98\x89u\xe40%\xb6\x19\'\x8c\xc4\x89\xca\x07\x0e\x1b!\x91UIFU%C\x994!DI\xd2\xfa\xf0\xf1N8W\xde\x13A\xf5\x9cr%?\x9f3;I45A\xd1\x8bT\xb1\xa4\xc7\x8d\x1a\\"\xad\xa1\xabyBg\x15\xb9l\x88\x88\x91k"\x94\xa4\xd4\x89\xae*\xa6\x0b\x10\x0c\xd6\xd4m\xe86\xec\xb5j\x8a\x86j\';\xca.\x01I\xf2\xaaJ\xe8\x88\x8cU+t3\xfb\x0c\n\xa33\x13r2\r\x16\xe0\xb3(\xbf\x1d\x83r\xe7M\xf0D\x1365\xd8\x88\xd3\xa4\x92\xcb2\x06\x04\\\xc1\xb0\xea//\xbek&\xd8\xe6+t\xe5\xa1\x13\xada\x16\xder5"w]\xa2i\xb7[\x97R \xe2IT\xcd;Z\x04dk4\xad\x8a\t\xd3\x81z\x10\xf1:^`\xab\x1f\xc5\xdc\x91N\x14$+\x9e\xae\xd3\x80' + EMPTY_DATA = 'BZh9\x17rE8P\x90\x00\x00\x00\x00' + + with open(findfile("testbz2_bigmem.bz2"), "rb") as f: + DATA_BIGMEM = f.read() if has_cmdline_bunzip2: def decompress(self, data): @@ -43,6 +47,7 @@ def decompress(self, data): return bz2.decompress(data) + class BZ2FileTest(BaseTest): "Test BZ2File type miscellaneous methods." @@ -323,6 +328,24 @@ self.assertRaises(ValueError, f.readline) self.assertRaises(ValueError, f.readlines) + def test_read_truncated(self): + # Drop the eos_magic field (6 bytes) and CRC (4 bytes). + truncated = self.DATA[:-10] + with open(self.filename, 'wb') as f: + f.write(truncated) + with BZ2File(self.filename) as f: + self.assertRaises(EOFError, f.read) + with BZ2File(self.filename) as f: + self.assertEqual(f.read(len(self.TEXT)), self.TEXT) + self.assertRaises(EOFError, f.read, 1) + # Incomplete 4-byte file header, and block header of at least 146 bits. + for i in range(22): + with open(self.filename, 'wb') as f: + f.write(truncated[:i]) + with BZ2File(self.filename) as f: + self.assertRaises(EOFError, f.read, 1) + + class BZ2CompressorTest(BaseTest): def testCompress(self): # "Test BZ2Compressor.compress()/flush()" @@ -332,6 +355,13 @@ data += bz2c.flush() self.assertEqual(self.decompress(data), self.TEXT) + def testCompressEmptyString(self): + # "Test BZ2Compressor.compress()/flush() of empty string" + bz2c = BZ2Compressor() + data = bz2c.compress('') + data += bz2c.flush() + self.assertEqual(data, self.EMPTY_DATA) + def testCompressChunks10(self): # "Test BZ2Compressor.compress()/flush() with chunks of 10 bytes" bz2c = BZ2Compressor() @@ -346,6 +376,17 @@ data += bz2c.flush() self.assertEqual(self.decompress(data), self.TEXT) + @bigmemtest(_4G, memuse=1.25) + def testBigmem(self, size): + text = "a" * size + bz2c = bz2.BZ2Compressor() + data = bz2c.compress(text) + bz2c.flush() + del text + text = self.decompress(data) + self.assertEqual(len(text), size) + self.assertEqual(text.strip("a"), "") + + class BZ2DecompressorTest(BaseTest): def test_Constructor(self): self.assertRaises(TypeError, BZ2Decompressor, 42) @@ -383,6 +424,16 @@ bz2d = BZ2Decompressor() text = bz2d.decompress(self.DATA) self.assertRaises(EOFError, bz2d.decompress, "anything") + self.assertRaises(EOFError, bz2d.decompress, "") + + @bigmemtest(_4G, memuse=1.25) + def testBigmem(self, size): + # Issue #14398: decompression fails when output data is >=2GB. + if size < _4G: + self.skipTest("Test needs 5GB of memory to run.") + text = bz2.BZ2Decompressor().decompress(self.DATA_BIGMEM) + self.assertEqual(len(text), _4G) + self.assertEqual(text.strip("\0"), "") class FuncTest(BaseTest): @@ -393,6 +444,11 @@ data = bz2.compress(self.TEXT) self.assertEqual(self.decompress(data), self.TEXT) + def testCompressEmptyString(self): + # "Test compress() of empty string" + text = bz2.compress('') + self.assertEqual(text, self.EMPTY_DATA) + def testDecompress(self): # "Test decompress() function" text = bz2.decompress(self.DATA) @@ -403,10 +459,33 @@ text = bz2.decompress("") self.assertEqual(text, "") + def testDecompressToEmptyString(self): + # "Test decompress() of minimal bz2 data to empty string" + text = bz2.decompress(self.EMPTY_DATA) + self.assertEqual(text, '') + def testDecompressIncomplete(self): # "Test decompress() function with incomplete data" self.assertRaises(ValueError, bz2.decompress, self.DATA[:-10]) + @bigmemtest(_4G, memuse=1.25) + def testCompressBigmem(self, size): + text = "a" * size + data = bz2.compress(text) + del text + text = self.decompress(data) + self.assertEqual(len(text), size) + self.assertEqual(text.strip("a"), "") + + @bigmemtest(_4G, memuse=1.25) + def testDecompressBigmem(self, size): + # Issue #14398: decompression fails when output data is >=2GB. + if size < _4G: + self.skipTest("Test needs 5GB of memory to run.") + text = bz2.decompress(self.DATA_BIGMEM) + self.assertEqual(len(text), _4G) + self.assertEqual(text.strip("\0"), "") + def test_main(): test_support.run_unittest( BZ2FileTest, diff --git a/lib-python/2.7/test/test_calendar.py b/lib-python/2.7/test/test_calendar.py --- a/lib-python/2.7/test/test_calendar.py +++ b/lib-python/2.7/test/test_calendar.py @@ -3,6 +3,7 @@ from test import test_support import locale +import datetime result_2004_text = """ @@ -254,13 +255,30 @@ # (it is still not thread-safe though) old_october = calendar.TextCalendar().formatmonthname(2010, 10, 10) try: - calendar.LocaleTextCalendar(locale='').formatmonthname(2010, 10, 10) + cal = calendar.LocaleTextCalendar(locale='') + local_weekday = cal.formatweekday(1, 10) + local_month = cal.formatmonthname(2010, 10, 10) except locale.Error: # cannot set the system default locale -- skip rest of test - return - calendar.LocaleHTMLCalendar(locale='').formatmonthname(2010, 10) + raise unittest.SkipTest('cannot set the system default locale') + # should be encodable + local_weekday.encode('utf-8') + local_month.encode('utf-8') + self.assertEqual(len(local_weekday), 10) + self.assertGreaterEqual(len(local_month), 10) + cal = calendar.LocaleHTMLCalendar(locale='') + local_weekday = cal.formatweekday(1) + local_month = cal.formatmonthname(2010, 10) + # should be encodable + local_weekday.encode('utf-8') + local_month.encode('utf-8') new_october = calendar.TextCalendar().formatmonthname(2010, 10, 10) - self.assertEquals(old_october, new_october) + self.assertEqual(old_october, new_october) + + def test_itermonthdates(self): + # ensure itermonthdates doesn't overflow after datetime.MAXYEAR + # see #15421 + list(calendar.Calendar().itermonthdates(datetime.MAXYEAR, 12)) class MonthCalendarTestCase(unittest.TestCase): diff --git a/lib-python/2.7/test/test_capi.py b/lib-python/2.7/test/test_capi.py --- a/lib-python/2.7/test/test_capi.py +++ b/lib-python/2.7/test/test_capi.py @@ -8,8 +8,10 @@ import unittest from test import test_support try: + import thread import threading except ImportError: + thread = None threading = None import _testcapi @@ -96,8 +98,32 @@ self.pendingcalls_wait(l, n) + at unittest.skipUnless(threading and thread, 'Threading required for this test.') +class TestThreadState(unittest.TestCase): + + @test_support.reap_threads + def test_thread_state(self): + # some extra thread-state tests driven via _testcapi + def target(): + idents = [] + + def callback(): + idents.append(thread.get_ident()) + + _testcapi._test_thread_state(callback) + a = b = callback + time.sleep(1) + # Check our main thread is in the list exactly 3 times. + self.assertEqual(idents.count(thread.get_ident()), 3, + "Couldn't find main thread correctly in the list") + + target() + t = threading.Thread(target=target) + t.start() + t.join() + + def test_main(): - for name in dir(_testcapi): if name.startswith('test_'): test = getattr(_testcapi, name) @@ -108,33 +134,7 @@ except _testcapi.error: raise test_support.TestFailed, sys.exc_info()[1] - # some extra thread-state tests driven via _testcapi - def TestThreadState(): - if test_support.verbose: - print "auto-thread-state" - - idents = [] - - def callback(): - idents.append(thread.get_ident()) - - _testcapi._test_thread_state(callback) - a = b = callback - time.sleep(1) - # Check our main thread is in the list exactly 3 times. - if idents.count(thread.get_ident()) != 3: - raise test_support.TestFailed, \ - "Couldn't find main thread correctly in the list" - - if threading: - import thread - import time - TestThreadState() - t=threading.Thread(target=TestThreadState) - t.start() - t.join() - - test_support.run_unittest(TestPendingCalls) + test_support.run_unittest(TestPendingCalls, TestThreadState) if __name__ == "__main__": test_main() diff --git a/lib-python/2.7/test/test_cmd.py b/lib-python/2.7/test/test_cmd.py --- a/lib-python/2.7/test/test_cmd.py +++ b/lib-python/2.7/test/test_cmd.py @@ -84,11 +84,11 @@ Documented commands (type help ): ======================================== - add + add help Undocumented commands: ====================== - exit help shell + exit shell Test for the function print_topics(): @@ -125,11 +125,11 @@ Documented commands (type help ): ======================================== - add + add help Undocumented commands: ====================== - exit help shell + exit shell help text for add Hello from postloop diff --git a/lib-python/2.7/test/test_cmd_line.py b/lib-python/2.7/test/test_cmd_line.py --- a/lib-python/2.7/test/test_cmd_line.py +++ b/lib-python/2.7/test/test_cmd_line.py @@ -2,9 +2,13 @@ # All tests are executed with environment variables ignored # See test_cmd_line_script.py for testing of script execution -import test.test_support, unittest +import test.test_support import sys -from test.script_helper import spawn_python, kill_python, python_exit_code +import unittest +from test.script_helper import ( + assert_python_ok, assert_python_failure, spawn_python, kill_python, + python_exit_code +) class CmdLineTest(unittest.TestCase): @@ -101,6 +105,36 @@ data = self.start_python('-R', '-c', code) self.assertTrue('hash_randomization=1' in data) + def test_del___main__(self): + # Issue #15001: PyRun_SimpleFileExFlags() did crash because it kept a + # borrowed reference to the dict of __main__ module and later modify + # the dict whereas the module was destroyed + filename = test.test_support.TESTFN + self.addCleanup(test.test_support.unlink, filename) + with open(filename, "w") as script: + print >>script, "import sys" + print >>script, "del sys.modules['__main__']" + assert_python_ok(filename) + + def test_unknown_options(self): + rc, out, err = assert_python_failure('-E', '-z') + self.assertIn(b'Unknown option: -z', err) + self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) + self.assertEqual(b'', out) + # Add "without='-E'" to prevent _assert_python to append -E + # to env_vars and change the output of stderr + rc, out, err = assert_python_failure('-z', without='-E') + self.assertIn(b'Unknown option: -z', err) + self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) + self.assertEqual(b'', out) + rc, out, err = assert_python_failure('-a', '-z', without='-E') + self.assertIn(b'Unknown option: -a', err) + # only the first unknown option is reported + self.assertNotIn(b'Unknown option: -z', err) + self.assertEqual(err.splitlines().count(b'Unknown option: -a'), 1) + self.assertEqual(b'', out) + + def test_main(): test.test_support.run_unittest(CmdLineTest) test.test_support.reap_children() diff --git a/lib-python/2.7/test/test_cmd_line_script.py b/lib-python/2.7/test/test_cmd_line_script.py --- a/lib-python/2.7/test/test_cmd_line_script.py +++ b/lib-python/2.7/test/test_cmd_line_script.py @@ -6,11 +6,14 @@ import test.test_support from test.script_helper import (run_python, temp_dir, make_script, compile_script, - make_pkg, make_zip_script, make_zip_pkg) + assert_python_failure, make_pkg, + make_zip_script, make_zip_pkg) verbose = test.test_support.verbose +example_args = ['test1', 'test2', 'test3'] + test_source = """\ # Script may be run with optimisation enabled, so don't rely on assert # statements being executed @@ -204,6 +207,19 @@ launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg') self._check_import_error(launch_name, msg) + def test_dash_m_error_code_is_one(self): + # If a module is invoked with the -m command line flag + # and results in an error that the return code to the + # shell is '1' + with temp_dir() as script_dir: + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir) + script_name = _make_test_script(pkg_dir, 'other', "if __name__ == '__main__': raise ValueError") + rc, out, err = assert_python_failure('-m', 'test_pkg.other', *example_args) + if verbose > 1: + print(out) + self.assertEqual(rc, 1) + def test_main(): test.test_support.run_unittest(CmdLineTest) diff --git a/lib-python/2.7/test/test_codeccallbacks.py b/lib-python/2.7/test/test_codeccallbacks.py --- a/lib-python/2.7/test/test_codeccallbacks.py +++ b/lib-python/2.7/test/test_codeccallbacks.py @@ -262,12 +262,12 @@ self.assertEqual( "\\u3042\u3xxx".decode("unicode-escape", "test.handler1"), - u"\u3042[<92><117><51><120>]xx" + u"\u3042[<92><117><51>]xxx" ) self.assertEqual( "\\u3042\u3xx".decode("unicode-escape", "test.handler1"), - u"\u3042[<92><117><51><120><120>]" + u"\u3042[<92><117><51>]xx" ) self.assertEqual( @@ -717,7 +717,7 @@ raise ValueError self.assertRaises(UnicodeError, codecs.charmap_decode, "\xff", "strict", {0xff: None}) self.assertRaises(ValueError, codecs.charmap_decode, "\xff", "strict", D()) - self.assertRaises(TypeError, codecs.charmap_decode, "\xff", "strict", {0xff: sys.maxunicode+1}) + self.assertRaises(TypeError, codecs.charmap_decode, "\xff", "strict", {0xff: 0x110000}) def test_encodehelper(self): # enhance coverage of: diff --git a/lib-python/2.7/test/test_codecs.py b/lib-python/2.7/test/test_codecs.py --- a/lib-python/2.7/test/test_codecs.py +++ b/lib-python/2.7/test/test_codecs.py @@ -4,6 +4,11 @@ import locale import sys, StringIO, _testcapi +def coding_checker(self, coder): + def check(input, expect): + self.assertEqual(coder(input), (expect, len(input))) + return check + class Queue(object): """ queue: write bytes at one end, read bytes from the other end @@ -281,7 +286,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", # first byte of BOM read u"", # second byte of BOM read @@ -303,6 +308,10 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) @@ -331,7 +340,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", u"", @@ -349,6 +358,10 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) @@ -371,7 +384,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", u"", @@ -389,6 +402,10 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) @@ -439,7 +456,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", # first byte of BOM read u"", # second byte of BOM read => byteorder known @@ -451,6 +468,10 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) @@ -481,7 +502,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", u"\x00", @@ -491,18 +512,34 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) def test_errors(self): - self.assertRaises(UnicodeDecodeError, codecs.utf_16_le_decode, "\xff", "strict", True) + tests = [ + (b'\xff', u'\ufffd'), + (b'A\x00Z', u'A\ufffd'), + (b'A\x00B\x00C\x00D\x00Z', u'ABCD\ufffd'), + (b'\x00\xd8', u'\ufffd'), + (b'\x00\xd8A', u'\ufffd'), + (b'\x00\xd8A\x00', u'\ufffdA'), + (b'\x00\xdcA\x00', u'\ufffdA'), + ] + for raw, expected in tests: + self.assertRaises(UnicodeDecodeError, codecs.utf_16_le_decode, + raw, 'strict', True) + self.assertEqual(raw.decode('utf-16le', 'replace'), expected) class UTF16BETest(ReadTest): encoding = "utf-16-be" def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", u"\x00", @@ -512,18 +549,34 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) def test_errors(self): - self.assertRaises(UnicodeDecodeError, codecs.utf_16_be_decode, "\xff", "strict", True) + tests = [ + (b'\xff', u'\ufffd'), + (b'\x00A\xff', u'A\ufffd'), + (b'\x00A\x00B\x00C\x00DZ', u'ABCD\ufffd'), + (b'\xd8\x00', u'\ufffd'), + (b'\xd8\x00\xdc', u'\ufffd'), + (b'\xd8\x00\x00A', u'\ufffdA'), + (b'\xdc\x00\x00A', u'\ufffdA'), + ] + for raw, expected in tests: + self.assertRaises(UnicodeDecodeError, codecs.utf_16_be_decode, + raw, 'strict', True) + self.assertEqual(raw.decode('utf-16be', 'replace'), expected) class UTF8Test(ReadTest): encoding = "utf-8" def test_partial(self): self.check_partial( - u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff\U00010000", [ u"\x00", u"\x00", @@ -536,6 +589,10 @@ u"\x00\xff\u07ff\u0800", u"\x00\xff\u07ff\u0800", u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff\U00010000", ] ) @@ -595,7 +652,7 @@ def test_partial(self): self.check_partial( - u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff\U00010000", [ u"", u"", @@ -614,6 +671,10 @@ u"\ufeff\x00\xff\u07ff\u0800", u"\ufeff\x00\xff\u07ff\u0800", u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff\U00010000", ] ) @@ -674,6 +735,54 @@ def test_empty(self): self.assertEqual(codecs.escape_decode(""), ("", 0)) + def test_raw(self): + decode = codecs.escape_decode + for b in range(256): + b = chr(b) + if b != '\\': + self.assertEqual(decode(b + '0'), (b + '0', 2)) + + def test_escape(self): + decode = codecs.escape_decode + check = coding_checker(self, decode) + check(b"[\\\n]", b"[]") + check(br'[\"]', b'["]') + check(br"[\']", b"[']") + check(br"[\\]", br"[\]") + check(br"[\a]", b"[\x07]") + check(br"[\b]", b"[\x08]") + check(br"[\t]", b"[\x09]") + check(br"[\n]", b"[\x0a]") + check(br"[\v]", b"[\x0b]") + check(br"[\f]", b"[\x0c]") + check(br"[\r]", b"[\x0d]") + check(br"[\7]", b"[\x07]") + check(br"[\8]", br"[\8]") + check(br"[\78]", b"[\x078]") + check(br"[\41]", b"[!]") + check(br"[\418]", b"[!8]") + check(br"[\101]", b"[A]") + check(br"[\1010]", b"[A0]") + check(br"[\501]", b"[A]") + check(br"[\x41]", b"[A]") + check(br"[\X41]", br"[\X41]") + check(br"[\x410]", b"[A0]") + for b in range(256): + b = chr(b) + if b not in '\n"\'\\abtnvfr01234567x': + check('\\' + b, '\\' + b) + + def test_errors(self): + decode = codecs.escape_decode + self.assertRaises(ValueError, decode, br"\x") + self.assertRaises(ValueError, decode, br"[\x]") + self.assertEqual(decode(br"[\x]\x", "ignore"), (b"[]", 6)) + self.assertEqual(decode(br"[\x]\x", "replace"), (b"[?]?", 6)) + self.assertRaises(ValueError, decode, br"\x0") + self.assertRaises(ValueError, decode, br"[\x0]") + self.assertEqual(decode(br"[\x0]\x0", "ignore"), (b"[]", 8)) + self.assertEqual(decode(br"[\x0]\x0", "replace"), (b"[?]?", 8)) + class RecodingTest(unittest.TestCase): def test_recoding(self): f = StringIO.StringIO() @@ -1495,6 +1604,14 @@ (u"abc", 3) ) + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, b"\x00\x01\x02", "strict", u"ab" + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", u"ab\ufffe" + ) + self.assertEqual( codecs.charmap_decode("\x00\x01\x02", "replace", u"ab"), (u"ab\ufffd", 3) @@ -1521,6 +1638,149 @@ (u"", len(allbytes)) ) + def test_decode_with_int2str_map(self): + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: u'a', 1: u'b', 2: u'c'}), + (u"abc", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: u'Aa', 1: u'Bb', 2: u'Cc'}), + (u"AaBbCc", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: u'\U0010FFFF', 1: u'b', 2: u'c'}), + (u"\U0010FFFFbc", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: u'a', 1: u'b', 2: u''}), + (u"ab", 3) + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: u'a', 1: u'b'} + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: u'a', 1: u'b', 2: None} + ) + + # Issue #14850 + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: u'a', 1: u'b', 2: u'\ufffe'} + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: u'a', 1: u'b'}), + (u"ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: u'a', 1: u'b', 2: None}), + (u"ab\ufffd", 3) + ) + + # Issue #14850 + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: u'a', 1: u'b', 2: u'\ufffe'}), + (u"ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: u'a', 1: u'b'}), + (u"ab", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: u'a', 1: u'b', 2: None}), + (u"ab", 3) + ) + + # Issue #14850 + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: u'a', 1: u'b', 2: u'\ufffe'}), + (u"ab", 3) + ) + + allbytes = "".join(chr(i) for i in xrange(256)) + self.assertEqual( + codecs.charmap_decode(allbytes, "ignore", {}), + (u"", len(allbytes)) + ) + + def test_decode_with_int2int_map(self): + a = ord(u'a') + b = ord(u'b') + c = ord(u'c') + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: a, 1: b, 2: c}), + (u"abc", 3) + ) + + # Issue #15379 + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: 0x10FFFF, 1: b, 2: c}), + (u"\U0010FFFFbc", 3) + ) + + self.assertRaises(TypeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: 0x110000, 1: b, 2: c} + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: a, 1: b}, + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: a, 1: b, 2: 0xFFFE}, + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: a, 1: b}), + (u"ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: a, 1: b, 2: 0xFFFE}), + (u"ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: a, 1: b}), + (u"ab", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: a, 1: b, 2: 0xFFFE}), + (u"ab", 3) + ) + + class WithStmtTest(unittest.TestCase): def test_encodedfile(self): f = StringIO.StringIO("\xc3\xbc") @@ -1535,6 +1795,134 @@ self.assertEqual(srw.read(), u"\xfc") +class UnicodeEscapeTest(unittest.TestCase): + def test_empty(self): + self.assertEqual(codecs.unicode_escape_encode(u""), ("", 0)) + self.assertEqual(codecs.unicode_escape_decode(""), (u"", 0)) + + def test_raw_encode(self): + encode = codecs.unicode_escape_encode + for b in range(32, 127): + if b != ord('\\'): + self.assertEqual(encode(unichr(b)), (chr(b), 1)) + + def test_raw_decode(self): + decode = codecs.unicode_escape_decode + for b in range(256): + if b != ord('\\'): + self.assertEqual(decode(chr(b) + '0'), (unichr(b) + u'0', 2)) + + def test_escape_encode(self): + encode = codecs.unicode_escape_encode + check = coding_checker(self, encode) + check(u'\t', r'\t') + check(u'\n', r'\n') + check(u'\r', r'\r') + check(u'\\', r'\\') + for b in range(32): + if chr(b) not in '\t\n\r': + check(unichr(b), '\\x%02x' % b) + for b in range(127, 256): + check(unichr(b), '\\x%02x' % b) + check(u'\u20ac', r'\u20ac') + check(u'\U0001d120', r'\U0001d120') + + def test_escape_decode(self): + decode = codecs.unicode_escape_decode + check = coding_checker(self, decode) + check("[\\\n]", u"[]") + check(r'[\"]', u'["]') + check(r"[\']", u"[']") + check(r"[\\]", ur"[\]") + check(r"[\a]", u"[\x07]") + check(r"[\b]", u"[\x08]") + check(r"[\t]", u"[\x09]") + check(r"[\n]", u"[\x0a]") + check(r"[\v]", u"[\x0b]") + check(r"[\f]", u"[\x0c]") + check(r"[\r]", u"[\x0d]") + check(r"[\7]", u"[\x07]") + check(r"[\8]", ur"[\8]") + check(r"[\78]", u"[\x078]") + check(r"[\41]", u"[!]") + check(r"[\418]", u"[!8]") + check(r"[\101]", u"[A]") + check(r"[\1010]", u"[A0]") + check(r"[\x41]", u"[A]") + check(r"[\x410]", u"[A0]") + check(r"\u20ac", u"\u20ac") + check(r"\U0001d120", u"\U0001d120") + for b in range(256): + if chr(b) not in '\n"\'\\abtnvfr01234567xuUN': + check('\\' + chr(b), u'\\' + unichr(b)) + + def test_decode_errors(self): + decode = codecs.unicode_escape_decode + for c, d in ('x', 2), ('u', 4), ('U', 4): + for i in range(d): + self.assertRaises(UnicodeDecodeError, decode, + "\\" + c + "0"*i) + self.assertRaises(UnicodeDecodeError, decode, + "[\\" + c + "0"*i + "]") + data = "[\\" + c + "0"*i + "]\\" + c + "0"*i + self.assertEqual(decode(data, "ignore"), (u"[]", len(data))) + self.assertEqual(decode(data, "replace"), + (u"[\ufffd]\ufffd", len(data))) + self.assertRaises(UnicodeDecodeError, decode, r"\U00110000") + self.assertEqual(decode(r"\U00110000", "ignore"), (u"", 10)) + self.assertEqual(decode(r"\U00110000", "replace"), (u"\ufffd", 10)) + + +class RawUnicodeEscapeTest(unittest.TestCase): + def test_empty(self): + self.assertEqual(codecs.raw_unicode_escape_encode(u""), ("", 0)) + self.assertEqual(codecs.raw_unicode_escape_decode(""), (u"", 0)) + + def test_raw_encode(self): + encode = codecs.raw_unicode_escape_encode + for b in range(256): + self.assertEqual(encode(unichr(b)), (chr(b), 1)) + + def test_raw_decode(self): + decode = codecs.raw_unicode_escape_decode + for b in range(256): + self.assertEqual(decode(chr(b) + '0'), (unichr(b) + u'0', 2)) + + def test_escape_encode(self): + encode = codecs.raw_unicode_escape_encode + check = coding_checker(self, encode) + for b in range(256): + if chr(b) not in 'uU': + check(u'\\' + unichr(b), '\\' + chr(b)) + check(u'\u20ac', r'\u20ac') + check(u'\U0001d120', r'\U0001d120') + + def test_escape_decode(self): + decode = codecs.raw_unicode_escape_decode + check = coding_checker(self, decode) + for b in range(256): + if chr(b) not in 'uU': + check('\\' + chr(b), u'\\' + unichr(b)) + check(r"\u20ac", u"\u20ac") + check(r"\U0001d120", u"\U0001d120") + + def test_decode_errors(self): + decode = codecs.raw_unicode_escape_decode + for c, d in ('u', 4), ('U', 4): + for i in range(d): + self.assertRaises(UnicodeDecodeError, decode, + "\\" + c + "0"*i) + self.assertRaises(UnicodeDecodeError, decode, + "[\\" + c + "0"*i + "]") + data = "[\\" + c + "0"*i + "]\\" + c + "0"*i + self.assertEqual(decode(data, "ignore"), (u"[]", len(data))) + self.assertEqual(decode(data, "replace"), + (u"[\ufffd]\ufffd", len(data))) + self.assertRaises(UnicodeDecodeError, decode, r"\U00110000") + self.assertEqual(decode(r"\U00110000", "ignore"), (u"", 10)) + self.assertEqual(decode(r"\U00110000", "replace"), (u"\ufffd", 10)) + + class BomTest(unittest.TestCase): def test_seek0(self): data = u"1234567890" @@ -1620,6 +2008,8 @@ BasicStrTest, CharmapTest, WithStmtTest, + UnicodeEscapeTest, + RawUnicodeEscapeTest, BomTest, ) diff --git a/lib-python/2.7/test/test_codeop.py b/lib-python/2.7/test/test_codeop.py --- a/lib-python/2.7/test/test_codeop.py +++ b/lib-python/2.7/test/test_codeop.py @@ -50,7 +50,7 @@ '''succeed iff str is the start of an invalid piece of code''' try: compile_command(str,symbol=symbol) - self.fail("No exception thrown for invalid code") + self.fail("No exception raised for invalid code") except SyntaxError: self.assertTrue(is_syntax) except OverflowError: diff --git a/lib-python/2.7/test/test_compile.py b/lib-python/2.7/test/test_compile.py --- a/lib-python/2.7/test/test_compile.py +++ b/lib-python/2.7/test/test_compile.py @@ -61,6 +61,34 @@ except SyntaxError: pass + def test_exec_functional_style(self): + # Exec'ing a tuple of length 2 works. + g = {'b': 2} + exec("a = b + 1", g) + self.assertEqual(g['a'], 3) + + # As does exec'ing a tuple of length 3. + l = {'b': 3} + g = {'b': 5, 'c': 7} + exec("a = b + c", g, l) + self.assertNotIn('a', g) + self.assertEqual(l['a'], 10) + + # Tuples not of length 2 or 3 are invalid. + with self.assertRaises(TypeError): + exec("a = b + 1",) + + with self.assertRaises(TypeError): + exec("a = b + 1", {}, {}, {}) + + # Can't mix and match the two calling forms. + g = {'a': 3, 'b': 4} + l = {} + with self.assertRaises(TypeError): + exec("a = b + 1", g) in g + with self.assertRaises(TypeError): + exec("a = b + 1", g, l) in g, l + def test_exec_with_general_mapping_for_locals(self): class M: diff --git a/lib-python/2.7/test/test_cookie.py b/lib-python/2.7/test/test_cookie.py --- a/lib-python/2.7/test/test_cookie.py +++ b/lib-python/2.7/test/test_cookie.py @@ -64,13 +64,13 @@ # loading 'expires' C = Cookie.SimpleCookie() - C.load('Customer="W"; expires=Wed, 01-Jan-2010 00:00:00 GMT') + C.load('Customer="W"; expires=Wed, 01 Jan 2010 00:00:00 GMT') self.assertEqual(C['Customer']['expires'], - 'Wed, 01-Jan-2010 00:00:00 GMT') + 'Wed, 01 Jan 2010 00:00:00 GMT') C = Cookie.SimpleCookie() - C.load('Customer="W"; expires=Wed, 01-Jan-98 00:00:00 GMT') + C.load('Customer="W"; expires=Wed, 01 Jan 98 00:00:00 GMT') self.assertEqual(C['Customer']['expires'], - 'Wed, 01-Jan-98 00:00:00 GMT') + 'Wed, 01 Jan 98 00:00:00 GMT') def test_extended_encode(self): # Issue 9824: some browsers don't follow the standard; we now @@ -90,9 +90,10 @@ def test_main(): run_unittest(CookieTests) - with check_warnings(('.+Cookie class is insecure; do not use it', - DeprecationWarning)): - run_doctest(Cookie) + if Cookie.__doc__ is not None: + with check_warnings(('.+Cookie class is insecure; do not use it', + DeprecationWarning)): + run_doctest(Cookie) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_cpickle.py b/lib-python/2.7/test/test_cpickle.py --- a/lib-python/2.7/test/test_cpickle.py +++ b/lib-python/2.7/test/test_cpickle.py @@ -1,7 +1,9 @@ import cPickle, unittest from cStringIO import StringIO -from test.pickletester import AbstractPickleTests, AbstractPickleModuleTests -from test.pickletester import AbstractPicklerUnpicklerObjectTests +from test.pickletester import (AbstractPickleTests, + AbstractPickleModuleTests, + AbstractPicklerUnpicklerObjectTests, + BigmemPickleTests) from test import test_support class cPickleTests(AbstractPickleTests, AbstractPickleModuleTests): @@ -101,6 +103,16 @@ pickler_class = cPickle.Pickler unpickler_class = cPickle.Unpickler +class cPickleBigmemPickleTests(BigmemPickleTests): + + def dumps(self, arg, proto=0, fast=0): + # Ignore fast + return cPickle.dumps(arg, proto) + + def loads(self, buf): + # Ignore fast + return cPickle.loads(buf) + class Node(object): pass @@ -133,6 +145,7 @@ cPickleFastPicklerTests, cPickleDeepRecursive, cPicklePicklerUnpicklerObjectTests, + cPickleBigmemPickleTests, ) if __name__ == "__main__": diff --git a/lib-python/2.7/test/test_csv.py b/lib-python/2.7/test/test_csv.py --- a/lib-python/2.7/test/test_csv.py +++ b/lib-python/2.7/test/test_csv.py @@ -243,6 +243,15 @@ self.assertRaises(csv.Error, self._read_test, ['a,b\nc,d'], []) self.assertRaises(csv.Error, self._read_test, ['a,b\r\nc,d'], []) + def test_read_eof(self): + self._read_test(['a,"'], [['a', '']]) + self._read_test(['"a'], [['a']]) + self._read_test(['^'], [['\n']], escapechar='^') + self.assertRaises(csv.Error, self._read_test, ['a,"'], [], strict=True) + self.assertRaises(csv.Error, self._read_test, ['"a'], [], strict=True) + self.assertRaises(csv.Error, self._read_test, + ['^'], [], escapechar='^', strict=True) + def test_read_escape(self): self._read_test(['a,\\b,c'], [['a', 'b', 'c']], escapechar='\\') self._read_test(['a,b\\,c'], [['a', 'b,c']], escapechar='\\') diff --git a/lib-python/2.7/test/test_decimal.py b/lib-python/2.7/test/test_decimal.py --- a/lib-python/2.7/test/test_decimal.py +++ b/lib-python/2.7/test/test_decimal.py @@ -1448,6 +1448,18 @@ self.assertEqual(float(d1), 66) self.assertEqual(float(d2), 15.32) + def test_nan_to_float(self): + # Test conversions of decimal NANs to float. + # See http://bugs.python.org/issue15544 + for s in ('nan', 'nan1234', '-nan', '-nan2468'): + f = float(Decimal(s)) + self.assertTrue(math.isnan(f)) + + def test_snan_to_float(self): + for s in ('snan', '-snan', 'snan1357', '-snan1234'): + d = Decimal(s) + self.assertRaises(ValueError, float, d) + def test_eval_round_trip(self): #with zero diff --git a/lib-python/2.7/test/test_deque.py b/lib-python/2.7/test/test_deque.py --- a/lib-python/2.7/test/test_deque.py +++ b/lib-python/2.7/test/test_deque.py @@ -6,6 +6,7 @@ import copy import cPickle as pickle import random +import struct BIG = 100000 @@ -517,6 +518,21 @@ gc.collect() self.assertTrue(ref() is None, "Cycle was not collected") + check_sizeof = test_support.check_sizeof + + @test_support.cpython_only + def test_sizeof(self): + BLOCKLEN = 62 + basesize = test_support.calcobjsize('2P4PlP') + blocksize = struct.calcsize('2P%dP' % BLOCKLEN) + self.assertEqual(object.__sizeof__(deque()), basesize) + check = self.check_sizeof + check(deque(), basesize + blocksize) + check(deque('a'), basesize + blocksize) + check(deque('a' * (BLOCKLEN // 2)), basesize + blocksize) + check(deque('a' * (BLOCKLEN // 2 + 1)), basesize + 2 * blocksize) + check(deque('a' * (42 * BLOCKLEN)), basesize + 43 * blocksize) + class TestVariousIteratorArgs(unittest.TestCase): def test_constructor(self): diff --git a/lib-python/2.7/test/test_descr.py b/lib-python/2.7/test/test_descr.py --- a/lib-python/2.7/test/test_descr.py +++ b/lib-python/2.7/test/test_descr.py @@ -1419,6 +1419,22 @@ self.assertEqual(x, spam.spamlist) self.assertEqual(a, a1) self.assertEqual(d, d1) + spam_cm = spam.spamlist.__dict__['classmeth'] + x2, a2, d2 = spam_cm(spam.spamlist, *a, **d) + self.assertEqual(x2, spam.spamlist) + self.assertEqual(a2, a1) + self.assertEqual(d2, d1) + class SubSpam(spam.spamlist): pass + x2, a2, d2 = spam_cm(SubSpam, *a, **d) + self.assertEqual(x2, SubSpam) + self.assertEqual(a2, a1) + self.assertEqual(d2, d1) + with self.assertRaises(TypeError): + spam_cm() + with self.assertRaises(TypeError): + spam_cm(spam.spamlist()) + with self.assertRaises(TypeError): + spam_cm(list) def test_staticmethods(self): # Testing static methods... @@ -4591,7 +4607,15 @@ pass Foo.__repr__ = Foo.__str__ foo = Foo() - str(foo) + self.assertRaises(RuntimeError, str, foo) + self.assertRaises(RuntimeError, repr, foo) + + def test_mixing_slot_wrappers(self): + class X(dict): + __setattr__ = dict.__setitem__ + x = X() + x.y = 42 + self.assertEqual(x["y"], 42) def test_cycle_through_dict(self): # See bug #1469629 diff --git a/lib-python/2.7/test/test_dict.py b/lib-python/2.7/test/test_dict.py --- a/lib-python/2.7/test/test_dict.py +++ b/lib-python/2.7/test/test_dict.py @@ -254,6 +254,14 @@ d = dict(zip(range(6), range(6))) self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6))) + class baddict3(dict): + def __new__(cls): + return d + d = {i : i for i in range(10)} + res = d.copy() + res.update(a=None, b=None, c=None) + self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res) + def test_copy(self): d = {1:1, 2:2, 3:3} self.assertEqual(d.copy(), {1:1, 2:2, 3:3}) diff --git a/lib-python/2.7/test/test_dictcomps.py b/lib-python/2.7/test/test_dictcomps.py --- a/lib-python/2.7/test/test_dictcomps.py +++ b/lib-python/2.7/test/test_dictcomps.py @@ -1,54 +1,91 @@ +import unittest -doctests = """ +from test import test_support as support - >>> k = "old value" - >>> { k: None for k in range(10) } - {0: None, 1: None, 2: None, 3: None, 4: None, 5: None, 6: None, 7: None, 8: None, 9: None} - >>> k - 'old value' +# For scope testing. +g = "Global variable" - >>> { k: k+10 for k in range(10) } - {0: 10, 1: 11, 2: 12, 3: 13, 4: 14, 5: 15, 6: 16, 7: 17, 8: 18, 9: 19} - >>> g = "Global variable" - >>> { k: g for k in range(10) } - {0: 'Global variable', 1: 'Global variable', 2: 'Global variable', 3: 'Global variable', 4: 'Global variable', 5: 'Global variable', 6: 'Global variable', 7: 'Global variable', 8: 'Global variable', 9: 'Global variable'} +class DictComprehensionTest(unittest.TestCase): - >>> { k: v for k in range(10) for v in range(10) if k == v } - {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9} + def test_basics(self): + expected = {0: 10, 1: 11, 2: 12, 3: 13, 4: 14, 5: 15, 6: 16, 7: 17, + 8: 18, 9: 19} + actual = {k: k + 10 for k in range(10)} + self.assertEqual(actual, expected) - >>> { k: v for v in range(10) for k in range(v*9, v*10) } - {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4, 38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6, 55: 6, 56: 6, 57: 6, 58: 6, 59: 6, 63: 7, 64: 7, 65: 7, 66: 7, 67: 7, 68: 7, 69: 7, 72: 8, 73: 8, 74: 8, 75: 8, 76: 8, 77: 8, 78: 8, 79: 8, 81: 9, 82: 9, 83: 9, 84: 9, 85: 9, 86: 9, 87: 9, 88: 9, 89: 9} + expected = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9} + actual = {k: v for k in range(10) for v in range(10) if k == v} + self.assertEqual(actual, expected) - >>> { x: y for y, x in ((1, 2), (3, 4)) } = 5 # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - SyntaxError: ... + def test_scope_isolation(self): + k = "Local Variable" - >>> { x: y for y, x in ((1, 2), (3, 4)) } += 5 # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - SyntaxError: ... + expected = {0: None, 1: None, 2: None, 3: None, 4: None, 5: None, + 6: None, 7: None, 8: None, 9: None} + actual = {k: None for k in range(10)} + self.assertEqual(actual, expected) + self.assertEqual(k, "Local Variable") -""" + expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4, + 38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6, + 55: 6, 56: 6, 57: 6, 58: 6, 59: 6, 63: 7, 64: 7, 65: 7, + 66: 7, 67: 7, 68: 7, 69: 7, 72: 8, 73: 8, 74: 8, 75: 8, + 76: 8, 77: 8, 78: 8, 79: 8, 81: 9, 82: 9, 83: 9, 84: 9, + 85: 9, 86: 9, 87: 9, 88: 9, 89: 9} + actual = {k: v for v in range(10) for k in range(v * 9, v * 10)} + self.assertEqual(k, "Local Variable") + self.assertEqual(actual, expected) -__test__ = {'doctests' : doctests} + def test_scope_isolation_from_global(self): + expected = {0: None, 1: None, 2: None, 3: None, 4: None, 5: None, + 6: None, 7: None, 8: None, 9: None} + actual = {g: None for g in range(10)} + self.assertEqual(actual, expected) + self.assertEqual(g, "Global variable") -def test_main(verbose=None): - import sys - from test import test_support - from test import test_dictcomps - test_support.run_doctest(test_dictcomps, verbose) + expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4, + 38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6, + 55: 6, 56: 6, 57: 6, 58: 6, 59: 6, 63: 7, 64: 7, 65: 7, + 66: 7, 67: 7, 68: 7, 69: 7, 72: 8, 73: 8, 74: 8, 75: 8, + 76: 8, 77: 8, 78: 8, 79: 8, 81: 9, 82: 9, 83: 9, 84: 9, + 85: 9, 86: 9, 87: 9, 88: 9, 89: 9} + actual = {g: v for v in range(10) for g in range(v * 9, v * 10)} + self.assertEqual(g, "Global variable") + self.assertEqual(actual, expected) - # verify reference counting - if verbose and hasattr(sys, "gettotalrefcount"): - import gc - counts = [None] * 5 - for i in range(len(counts)): - test_support.run_doctest(test_dictcomps, verbose) - gc.collect() - counts[i] = sys.gettotalrefcount() - print(counts) + def test_global_visibility(self): + expected = {0: 'Global variable', 1: 'Global variable', + 2: 'Global variable', 3: 'Global variable', + 4: 'Global variable', 5: 'Global variable', + 6: 'Global variable', 7: 'Global variable', + 8: 'Global variable', 9: 'Global variable'} + actual = {k: g for k in range(10)} + self.assertEqual(actual, expected) + + def test_local_visibility(self): + v = "Local variable" + expected = {0: 'Local variable', 1: 'Local variable', + 2: 'Local variable', 3: 'Local variable', + 4: 'Local variable', 5: 'Local variable', + 6: 'Local variable', 7: 'Local variable', + 8: 'Local variable', 9: 'Local variable'} + actual = {k: v for k in range(10)} + self.assertEqual(actual, expected) + self.assertEqual(v, "Local variable") + + def test_illegal_assignment(self): + with self.assertRaisesRegexp(SyntaxError, "can't assign"): + compile("{x: y for y, x in ((1, 2), (3, 4))} = 5", "", + "exec") + + with self.assertRaisesRegexp(SyntaxError, "can't assign"): + compile("{x: y for y, x in ((1, 2), (3, 4))} += 5", "", + "exec") + + +def test_main(): + support.run_unittest(__name__) if __name__ == "__main__": - test_main(verbose=True) + test_main() diff --git a/lib-python/2.7/test/test_doctest.py b/lib-python/2.7/test/test_doctest.py --- a/lib-python/2.7/test/test_doctest.py +++ b/lib-python/2.7/test/test_doctest.py @@ -2006,6 +2006,31 @@ >>> suite.run(unittest.TestResult()) + The module need not contain any doctest examples: + + >>> suite = doctest.DocTestSuite('test.sample_doctest_no_doctests') + >>> suite.run(unittest.TestResult()) + + + However, if DocTestSuite finds no docstrings, it raises an error: + + >>> try: + ... doctest.DocTestSuite('test.sample_doctest_no_docstrings') + ... except ValueError as e: + ... error = e + + >>> print(error.args[1]) + has no docstrings + + You can prevent this error by passing a DocTestFinder instance with + the `exclude_empty` keyword argument set to False: + + >>> finder = doctest.DocTestFinder(exclude_empty=False) + >>> suite = doctest.DocTestSuite('test.sample_doctest_no_docstrings', + ... test_finder=finder) + >>> suite.run(unittest.TestResult()) + + We can use the current module: >>> suite = test.sample_doctest.test_suite() @@ -2648,7 +2673,9 @@ from test import test_doctest # Ignore all warnings about the use of class Tester in this module. - deprecations = [("class Tester is deprecated", DeprecationWarning)] + deprecations = [] + if __debug__: + deprecations.append(("class Tester is deprecated", DeprecationWarning)) if sys.py3kwarning: deprecations += [("backquote not supported", SyntaxWarning), ("execfile.. not supported", DeprecationWarning)] diff --git a/lib-python/2.7/test/test_docxmlrpc.py b/lib-python/2.7/test/test_docxmlrpc.py --- a/lib-python/2.7/test/test_docxmlrpc.py +++ b/lib-python/2.7/test/test_docxmlrpc.py @@ -100,7 +100,7 @@ self.assertEqual(response.status, 200) self.assertEqual(response.getheader("Content-type"), "text/html") - # Server throws an exception if we don't start to read the data + # Server raises an exception if we don't start to read the data response.read() def test_invalid_get_response(self): diff --git a/lib-python/2.7/test/test_email.py b/lib-python/2.7/test/test_email.py --- a/lib-python/2.7/test/test_email.py +++ b/lib-python/2.7/test/test_email.py @@ -3,10 +3,12 @@ # The specific tests now live in Lib/email/test from email.test.test_email import suite +from email.test.test_email_renamed import suite as suite2 from test import test_support def test_main(): test_support.run_unittest(suite()) + test_support.run_unittest(suite2()) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_exceptions.py b/lib-python/2.7/test/test_exceptions.py --- a/lib-python/2.7/test/test_exceptions.py +++ b/lib-python/2.7/test/test_exceptions.py @@ -479,6 +479,18 @@ except AssertionError as e: self.assertEqual(str(e), "(3,)") + def test_bad_exception_clearing(self): + # See issue 16445: use of Py_XDECREF instead of Py_CLEAR in + # BaseException_set_message gave a possible way to segfault the + # interpreter. + class Nasty(str): + def __del__(message): + del e.message + + e = ValueError(Nasty("msg")) + e.args = () + del e.message + # Helper class used by TestSameStrAndUnicodeMsg class ExcWithOverriddenStr(Exception): diff --git a/lib-python/2.7/test/test_fcntl.py b/lib-python/2.7/test/test_fcntl.py --- a/lib-python/2.7/test/test_fcntl.py +++ b/lib-python/2.7/test/test_fcntl.py @@ -6,6 +6,7 @@ import os import struct import sys +import _testcapi import unittest from test.test_support import (verbose, TESTFN, unlink, run_unittest, import_module) @@ -81,6 +82,26 @@ rv = fcntl.fcntl(self.f, fcntl.F_SETLKW, lockdata) self.f.close() + def test_fcntl_bad_file(self): + class F: + def __init__(self, fn): + self.fn = fn + def fileno(self): + return self.fn + self.assertRaises(ValueError, fcntl.fcntl, -1, fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(ValueError, fcntl.fcntl, F(-1), fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(TypeError, fcntl.fcntl, 'spam', fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(TypeError, fcntl.fcntl, F('spam'), fcntl.F_SETFL, os.O_NONBLOCK) + # Issue 15989 + self.assertRaises(ValueError, fcntl.fcntl, _testcapi.INT_MAX + 1, + fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(ValueError, fcntl.fcntl, F(_testcapi.INT_MAX + 1), + fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(ValueError, fcntl.fcntl, _testcapi.INT_MIN - 1, + fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(ValueError, fcntl.fcntl, F(_testcapi.INT_MIN - 1), + fcntl.F_SETFL, os.O_NONBLOCK) + def test_fcntl_64_bit(self): # Issue #1309352: fcntl shouldn't fail when the third arg fits in a # C 'long' but not in a C 'int'. diff --git a/lib-python/2.7/test/test_file2k.py b/lib-python/2.7/test/test_file2k.py --- a/lib-python/2.7/test/test_file2k.py +++ b/lib-python/2.7/test/test_file2k.py @@ -2,6 +2,9 @@ import os import unittest import itertools +import select +import signal +import subprocess import time from array import array from weakref import proxy @@ -602,6 +605,148 @@ self._test_close_open_io(io_func) + at unittest.skipUnless(os.name == 'posix', 'test requires a posix system.') +class TestFileSignalEINTR(unittest.TestCase): + def _test_reading(self, data_to_write, read_and_verify_code, method_name, + universal_newlines=False): + """Generic buffered read method test harness to verify EINTR behavior. + + Also validates that Python signal handlers are run during the read. + + Args: + data_to_write: String to write to the child process for reading + before sending it a signal, confirming the signal was handled, + writing a final newline char and closing the infile pipe. + read_and_verify_code: Single "line" of code to read from a file + object named 'infile' and validate the result. This will be + executed as part of a python subprocess fed data_to_write. + method_name: The name of the read method being tested, for use in + an error message on failure. + universal_newlines: If True, infile will be opened in universal + newline mode in the child process. + """ + if universal_newlines: + # Test the \r\n -> \n conversion while we're at it. + data_to_write = data_to_write.replace('\n', '\r\n') + infile_setup_code = 'infile = os.fdopen(sys.stdin.fileno(), "rU")' + else: + infile_setup_code = 'infile = sys.stdin' + # Total pipe IO in this function is smaller than the minimum posix OS + # pipe buffer size of 512 bytes. No writer should block. + assert len(data_to_write) < 512, 'data_to_write must fit in pipe buf.' + + child_code = ( + 'import os, signal, sys ;' + 'signal.signal(' + 'signal.SIGINT, lambda s, f: sys.stderr.write("$\\n")) ;' + + infile_setup_code + ' ;' + + 'assert isinstance(infile, file) ;' + 'sys.stderr.write("Go.\\n") ;' + + read_and_verify_code) + reader_process = subprocess.Popen( + [sys.executable, '-c', child_code], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + # Wait for the signal handler to be installed. + go = reader_process.stderr.read(4) + if go != 'Go.\n': + reader_process.kill() + self.fail('Error from %s process while awaiting "Go":\n%s' % ( + method_name, go+reader_process.stderr.read())) + reader_process.stdin.write(data_to_write) + signals_sent = 0 + rlist = [] + # We don't know when the read_and_verify_code in our child is actually + # executing within the read system call we want to interrupt. This + # loop waits for a bit before sending the first signal to increase + # the likelihood of that. Implementations without correct EINTR + # and signal handling usually fail this test. + while not rlist: + rlist, _, _ = select.select([reader_process.stderr], (), (), 0.05) + reader_process.send_signal(signal.SIGINT) + # Give the subprocess time to handle it before we loop around and + # send another one. On OSX the second signal happening close to + # immediately after the first was causing the subprocess to crash + # via the OS's default SIGINT handler. + time.sleep(0.1) + signals_sent += 1 + if signals_sent > 200: + reader_process.kill() + self.fail("failed to handle signal during %s." % method_name) + # This assumes anything unexpected that writes to stderr will also + # write a newline. That is true of the traceback printing code. + signal_line = reader_process.stderr.readline() + if signal_line != '$\n': + reader_process.kill() + self.fail('Error from %s process while awaiting signal:\n%s' % ( + method_name, signal_line+reader_process.stderr.read())) + # We append a newline to our input so that a readline call can + # end on its own before the EOF is seen. + stdout, stderr = reader_process.communicate(input='\n') + if reader_process.returncode != 0: + self.fail('%s() process exited rc=%d.\nSTDOUT:\n%s\nSTDERR:\n%s' % ( + method_name, reader_process.returncode, stdout, stderr)) + + def test_readline(self, universal_newlines=False): + """file.readline must handle signals and not lose data.""" + self._test_reading( + data_to_write='hello, world!', + read_and_verify_code=( + 'line = infile.readline() ;' + 'expected_line = "hello, world!\\n" ;' + 'assert line == expected_line, (' + '"read %r expected %r" % (line, expected_line))' + ), + method_name='readline', + universal_newlines=universal_newlines) + + def test_readline_with_universal_newlines(self): + self.test_readline(universal_newlines=True) + + def test_readlines(self, universal_newlines=False): + """file.readlines must handle signals and not lose data.""" + self._test_reading( + data_to_write='hello\nworld!', + read_and_verify_code=( + 'lines = infile.readlines() ;' + 'expected_lines = ["hello\\n", "world!\\n"] ;' + 'assert lines == expected_lines, (' + '"readlines returned wrong data.\\n" ' + '"got lines %r\\nexpected %r" ' + '% (lines, expected_lines))' + ), + method_name='readlines', + universal_newlines=universal_newlines) + + def test_readlines_with_universal_newlines(self): + self.test_readlines(universal_newlines=True) + + def test_readall(self): + """Unbounded file.read() must handle signals and not lose data.""" + self._test_reading( + data_to_write='hello, world!abcdefghijklm', + read_and_verify_code=( + 'data = infile.read() ;' + 'expected_data = "hello, world!abcdefghijklm\\n";' + 'assert data == expected_data, (' + '"read %r expected %r" % (data, expected_data))' + ), + method_name='unbounded read') + + def test_readinto(self): + """file.readinto must handle signals and not lose data.""" + self._test_reading( + data_to_write='hello, world!', + read_and_verify_code=( + 'data = bytearray(50) ;' + 'num_read = infile.readinto(data) ;' + 'expected_data = "hello, world!\\n";' + 'assert data[:num_read] == expected_data, (' + '"read %r expected %r" % (data, expected_data))' + ), + method_name='readinto') + + class StdoutTests(unittest.TestCase): def test_move_stdout_on_write(self): @@ -678,7 +823,7 @@ # So get rid of it no matter what. try: run_unittest(AutoFileTests, OtherFileTests, FileSubclassTests, - FileThreadingTests, StdoutTests) + FileThreadingTests, TestFileSignalEINTR, StdoutTests) finally: if os.path.exists(TESTFN): os.unlink(TESTFN) diff --git a/lib-python/2.7/test/test_fileio.py b/lib-python/2.7/test/test_fileio.py --- a/lib-python/2.7/test/test_fileio.py +++ b/lib-python/2.7/test/test_fileio.py @@ -9,6 +9,8 @@ from array import array from weakref import proxy from functools import wraps +from UserList import UserList +import _testcapi from test.test_support import TESTFN, check_warnings, run_unittest, make_bad_fd from test.test_support import py3k_bytes as bytes @@ -71,6 +73,26 @@ n = self.f.readinto(a) self.assertEqual(array(b'b', [1, 2]), a[:n]) + def testWritelinesList(self): + l = [b'123', b'456'] + self.f.writelines(l) + self.f.close() + self.f = _FileIO(TESTFN, 'rb') + buf = self.f.read() + self.assertEqual(buf, b'123456') + + def testWritelinesUserList(self): + l = UserList([b'123', b'456']) + self.f.writelines(l) + self.f.close() + self.f = _FileIO(TESTFN, 'rb') + buf = self.f.read() + self.assertEqual(buf, b'123456') + + def testWritelinesError(self): + self.assertRaises(TypeError, self.f.writelines, [1, 2, 3]) + self.assertRaises(TypeError, self.f.writelines, None) + def test_none_args(self): self.f.write(b"hi\nbye\nabc") self.f.close() @@ -130,6 +152,14 @@ else: self.fail("Should have raised IOError") + @unittest.skipIf(os.name == 'nt', "test only works on a POSIX-like system") + def testOpenDirFD(self): + fd = os.open('.', os.O_RDONLY) + with self.assertRaises(IOError) as cm: + _FileIO(fd, 'r') + os.close(fd) + self.assertEqual(cm.exception.errno, errno.EISDIR) + #A set of functions testing that we get expected behaviour if someone has #manually closed the internal file descriptor. First, a decorator: def ClosedFD(func): @@ -314,6 +344,9 @@ if sys.platform == 'win32': import msvcrt self.assertRaises(IOError, msvcrt.get_osfhandle, make_bad_fd()) + # Issue 15989 + self.assertRaises(TypeError, _FileIO, _testcapi.INT_MAX + 1) + self.assertRaises(TypeError, _FileIO, _testcapi.INT_MIN - 1) def testBadModeArgument(self): # verify that we get a sensible error message for bad mode argument @@ -417,10 +450,22 @@ env = dict(os.environ) env[b'LC_CTYPE'] = b'C' _, out = run_python('-c', 'import _io; _io.FileIO(%r)' % filename, env=env) - if ('UnicodeEncodeError' not in out and - 'IOError: [Errno 2] No such file or directory' not in out): + if ('UnicodeEncodeError' not in out and not + ( ('IOError: [Errno 2] No such file or directory' in out) or + ('IOError: [Errno 22] Invalid argument' in out) ) ): self.fail('Bad output: %r' % out) + def testUnclosedFDOnException(self): + class MyException(Exception): pass + class MyFileIO(_FileIO): + def __setattr__(self, name, value): + if name == "name": + raise MyException("blocked setting name") + return super(MyFileIO, self).__setattr__(name, value) + fd = os.open(__file__, os.O_RDONLY) + self.assertRaises(MyException, MyFileIO, fd) + os.close(fd) # should not raise OSError(EBADF) + def test_main(): # Historically, these tests have been sloppy about removing TESTFN. # So get rid of it no matter what. diff --git a/lib-python/2.7/test/test_format.py b/lib-python/2.7/test/test_format.py --- a/lib-python/2.7/test/test_format.py +++ b/lib-python/2.7/test/test_format.py @@ -234,6 +234,16 @@ testformat('%g', 1.1, '1.1') testformat('%#g', 1.1, '1.10000') + # Regression test for http://bugs.python.org/issue15516. + class IntFails(object): + def __int__(self): + raise TestFailed + def __long__(self): + return 0 + + fst = IntFails() + testformat("%x", fst, '0') + # Test exception for unknown format characters if verbose: print 'Testing exceptions' diff --git a/lib-python/2.7/test/test_functools.py b/lib-python/2.7/test/test_functools.py --- a/lib-python/2.7/test/test_functools.py +++ b/lib-python/2.7/test/test_functools.py @@ -151,6 +151,23 @@ f_copy = pickle.loads(pickle.dumps(f)) self.assertEqual(signature(f), signature(f_copy)) + # Issue 6083: Reference counting bug + def test_setstate_refcount(self): + class BadSequence: + def __len__(self): + return 4 + def __getitem__(self, key): + if key == 0: + return max + elif key == 1: + return tuple(range(1000000)) + elif key in (2, 3): + return {} + raise IndexError + + f = self.thetype(object) + self.assertRaises(SystemError, f.__setstate__, BadSequence()) + class PartialSubclass(functools.partial): pass @@ -164,6 +181,7 @@ # the python version isn't picklable def test_pickle(self): pass + def test_setstate_refcount(self): pass class TestUpdateWrapper(unittest.TestCase): @@ -232,6 +250,7 @@ self.assertEqual(wrapper.attr, 'This is a different test') self.assertEqual(wrapper.dict_attr, f.dict_attr) + @test_support.requires_docstrings def test_builtin_update(self): # Test for bug #1576241 def wrapper(): @@ -258,7 +277,7 @@ self.assertEqual(wrapper.__name__, 'f') self.assertEqual(wrapper.attr, 'This is also a test') - @unittest.skipIf(not sys.flags.optimize <= 1, + @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_default_update_doc(self): wrapper = self._default_update() diff --git a/lib-python/2.7/test/test_gc.py b/lib-python/2.7/test/test_gc.py --- a/lib-python/2.7/test/test_gc.py +++ b/lib-python/2.7/test/test_gc.py @@ -1,9 +1,15 @@ import unittest from test.test_support import verbose, run_unittest import sys +import time import gc import weakref +try: + import threading +except ImportError: + threading = None + ### Support code ############################################################################### @@ -299,6 +305,69 @@ v = {1: v, 2: Ouch()} gc.disable() + @unittest.skipUnless(threading, "test meaningless on builds without threads") + def test_trashcan_threads(self): + # Issue #13992: trashcan mechanism should be thread-safe + NESTING = 60 + N_THREADS = 2 + + def sleeper_gen(): + """A generator that releases the GIL when closed or dealloc'ed.""" + try: + yield + finally: + time.sleep(0.000001) + + class C(list): + # Appending to a list is atomic, which avoids the use of a lock. + inits = [] + dels = [] + def __init__(self, alist): + self[:] = alist + C.inits.append(None) + def __del__(self): + # This __del__ is called by subtype_dealloc(). + C.dels.append(None) + # `g` will release the GIL when garbage-collected. This + # helps assert subtype_dealloc's behaviour when threads + # switch in the middle of it. + g = sleeper_gen() + next(g) + # Now that __del__ is finished, subtype_dealloc will proceed + # to call list_dealloc, which also uses the trashcan mechanism. + + def make_nested(): + """Create a sufficiently nested container object so that the + trashcan mechanism is invoked when deallocating it.""" + x = C([]) + for i in range(NESTING): + x = [C([x])] + del x + + def run_thread(): + """Exercise make_nested() in a loop.""" + while not exit: + make_nested() + + old_checkinterval = sys.getcheckinterval() + sys.setcheckinterval(3) + try: + exit = False + threads = [] + for i in range(N_THREADS): + t = threading.Thread(target=run_thread) + threads.append(t) + for t in threads: + t.start() + time.sleep(1.0) + exit = True + for t in threads: + t.join() + finally: + sys.setcheckinterval(old_checkinterval) + gc.collect() + self.assertEqual(len(C.inits), len(C.dels)) + def test_boom(self): class Boom: def __getattr__(self, someattribute): diff --git a/lib-python/2.7/test/test_gdb.py b/lib-python/2.7/test/test_gdb.py --- a/lib-python/2.7/test/test_gdb.py +++ b/lib-python/2.7/test/test_gdb.py @@ -19,19 +19,48 @@ # This is what "no gdb" looks like. There may, however, be other # errors that manifest this way too. raise unittest.SkipTest("Couldn't find gdb on the path") -gdb_version_number = re.search(r"^GNU gdb [^\d]*(\d+)\.", gdb_version) -if int(gdb_version_number.group(1)) < 7: +gdb_version_number = re.search("^GNU gdb [^\d]*(\d+)\.(\d)", gdb_version) +gdb_major_version = int(gdb_version_number.group(1)) +gdb_minor_version = int(gdb_version_number.group(2)) +if gdb_major_version < 7: raise unittest.SkipTest("gdb versions before 7.0 didn't support python embedding" " Saw:\n" + gdb_version) +# Location of custom hooks file in a repository checkout. +checkout_hook_path = os.path.join(os.path.dirname(sys.executable), + 'python-gdb.py') + +def run_gdb(*args, **env_vars): + """Runs gdb in --batch mode with the additional arguments given by *args. + + Returns its (stdout, stderr) + """ + if env_vars: + env = os.environ.copy() + env.update(env_vars) + else: + env = None + base_cmd = ('gdb', '--batch') + if (gdb_major_version, gdb_minor_version) >= (7, 4): + base_cmd += ('-iex', 'add-auto-load-safe-path ' + checkout_hook_path) + out, err = subprocess.Popen(base_cmd + args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, + ).communicate() + return out, err + # Verify that "gdb" was built with the embedded python support enabled: -cmd = "--eval-command=python import sys; print sys.version_info" -p = subprocess.Popen(["gdb", "--batch", cmd], - stdout=subprocess.PIPE) -gdbpy_version, _ = p.communicate() -if gdbpy_version == '': +gdbpy_version, _ = run_gdb("--eval-command=python import sys; print sys.version_info") +if not gdbpy_version: raise unittest.SkipTest("gdb not built with embedded python support") +# Verify that "gdb" can load our custom hooks. In theory this should never +# fail, but we don't handle the case of the hooks file not existing if the +# tests are run from an installed Python (we'll produce failures in that case). +cmd = ['--args', sys.executable] +_, gdbpy_errors = run_gdb('--args', sys.executable) +if "auto-loading has been declined" in gdbpy_errors: + msg = "gdb security settings prevent use of custom hooks: " + def python_is_optimized(): cflags = sysconfig.get_config_vars()['PY_CFLAGS'] final_opt = "" @@ -42,10 +71,7 @@ def gdb_has_frame_select(): # Does this build of gdb have gdb.Frame.select ? - cmd = "--eval-command=python print(dir(gdb.Frame))" - p = subprocess.Popen(["gdb", "--batch", cmd], - stdout=subprocess.PIPE) - stdout, _ = p.communicate() + stdout, _ = run_gdb("--eval-command=python print(dir(gdb.Frame))") m = re.match(r'.*\[(.*)\].*', stdout) if not m: raise unittest.SkipTest("Unable to parse output from gdb.Frame.select test") @@ -58,21 +84,6 @@ """Test that the debugger can debug Python.""" - def run_gdb(self, *args, **env_vars): - """Runs gdb with the command line given by *args. - - Returns its stdout, stderr - """ - if env_vars: - env = os.environ.copy() - env.update(env_vars) - else: - env = None - out, err = subprocess.Popen( - args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env - ).communicate() - return out, err - def get_stack_trace(self, source=None, script=None, breakpoint='PyObject_Print', cmds_after_breakpoint=None, @@ -129,7 +140,7 @@ # print ' '.join(args) # Use "args" to invoke gdb, capturing stdout, stderr: - out, err = self.run_gdb(*args, PYTHONHASHSEED='0') + out, err = run_gdb(*args, PYTHONHASHSEED='0') # Ignore some noise on stderr due to the pending breakpoint: err = err.replace('Function "%s" not defined.\n' % breakpoint, '') @@ -141,6 +152,16 @@ err = err.replace("warning: Cannot initialize thread debugging" " library: Debugger service failed\n", '') + err = err.replace('warning: Could not load shared library symbols for ' + 'linux-vdso.so.1.\n' + 'Do you need "set solib-search-path" or ' + '"set sysroot"?\n', + '') + err = err.replace('warning: Could not load shared library symbols for ' + 'linux-gate.so.1.\n' + 'Do you need "set solib-search-path" or ' + '"set sysroot"?\n', + '') # Ensure no unexpected error messages: self.assertEqual(err, '') diff --git a/lib-python/2.7/test/test_generators.py b/lib-python/2.7/test/test_generators.py --- a/lib-python/2.7/test/test_generators.py +++ b/lib-python/2.7/test/test_generators.py @@ -383,7 +383,8 @@ >>> [s for s in dir(i) if not s.startswith('_')] ['close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw'] ->>> print i.next.__doc__ +>>> from test.test_support import HAVE_DOCSTRINGS +>>> print(i.next.__doc__ if HAVE_DOCSTRINGS else 'x.next() -> the next value, or raise StopIteration') x.next() -> the next value, or raise StopIteration >>> iter(i) is i True diff --git a/lib-python/2.7/test/test_genexps.py b/lib-python/2.7/test/test_genexps.py --- a/lib-python/2.7/test/test_genexps.py +++ b/lib-python/2.7/test/test_genexps.py @@ -223,7 +223,8 @@ >>> set(attr for attr in dir(g) if not attr.startswith('__')) >= expected True - >>> print g.next.__doc__ + >>> from test.test_support import HAVE_DOCSTRINGS + >>> print(g.next.__doc__ if HAVE_DOCSTRINGS else 'x.next() -> the next value, or raise StopIteration') x.next() -> the next value, or raise StopIteration >>> import types >>> isinstance(g, types.GeneratorType) diff --git a/lib-python/2.7/test/test_glob.py b/lib-python/2.7/test/test_glob.py --- a/lib-python/2.7/test/test_glob.py +++ b/lib-python/2.7/test/test_glob.py @@ -1,8 +1,15 @@ -import unittest -from test.test_support import run_unittest, TESTFN import glob import os import shutil +import sys +import unittest + +from test.test_support import run_unittest, TESTFN + + +def fsdecode(s): + return unicode(s, sys.getfilesystemencoding()) + class GlobTests(unittest.TestCase): @@ -18,16 +25,19 @@ f.close() def setUp(self): - self.tempdir = TESTFN+"_dir" + self.tempdir = TESTFN + "_dir" self.mktemp('a', 'D') self.mktemp('aab', 'F') + self.mktemp('.aa', 'G') + self.mktemp('.bb', 'H') self.mktemp('aaa', 'zzzF') self.mktemp('ZZZ') self.mktemp('a', 'bcd', 'EF') self.mktemp('a', 'bcd', 'efg', 'ha') if hasattr(os, 'symlink'): os.symlink(self.norm('broken'), self.norm('sym1')) - os.symlink(self.norm('broken'), self.norm('sym2')) + os.symlink('broken', self.norm('sym2')) + os.symlink(os.path.join('a', 'bcd'), self.norm('sym3')) def tearDown(self): shutil.rmtree(self.tempdir) @@ -40,10 +50,16 @@ p = os.path.join(self.tempdir, pattern) res = glob.glob(p) self.assertEqual(list(glob.iglob(p)), res) + ures = [fsdecode(x) for x in res] + self.assertEqual(glob.glob(fsdecode(p)), ures) + self.assertEqual(list(glob.iglob(fsdecode(p))), ures) return res def assertSequencesEqual_noorder(self, l1, l2): + l1 = list(l1) + l2 = list(l2) self.assertEqual(set(l1), set(l2)) + self.assertEqual(sorted(l1), sorted(l2)) def test_glob_literal(self): eq = self.assertSequencesEqual_noorder @@ -52,20 +68,26 @@ eq(self.glob('aab'), [self.norm('aab')]) eq(self.glob('zymurgy'), []) + res = glob.glob('*') + self.assertEqual({type(r) for r in res}, {str}) + res = glob.glob(os.path.join(os.curdir, '*')) + self.assertEqual({type(r) for r in res}, {str}) + # test return types are unicode, but only if os.listdir # returns unicode filenames - uniset = set([unicode]) - tmp = os.listdir(u'.') - if set(type(x) for x in tmp) == uniset: - u1 = glob.glob(u'*') - u2 = glob.glob(u'./*') - self.assertEqual(set(type(r) for r in u1), uniset) - self.assertEqual(set(type(r) for r in u2), uniset) + tmp = os.listdir(fsdecode(os.curdir)) + if {type(x) for x in tmp} == {unicode}: + res = glob.glob(u'*') + self.assertEqual({type(r) for r in res}, {unicode}) + res = glob.glob(os.path.join(fsdecode(os.curdir), u'*')) + self.assertEqual({type(r) for r in res}, {unicode}) def test_glob_one_directory(self): eq = self.assertSequencesEqual_noorder eq(self.glob('a*'), map(self.norm, ['a', 'aab', 'aaa'])) eq(self.glob('*a'), map(self.norm, ['a', 'aaa'])) + eq(self.glob('.*'), map(self.norm, ['.aa', '.bb'])) + eq(self.glob('?aa'), map(self.norm, ['aaa'])) eq(self.glob('aa?'), map(self.norm, ['aaa', 'aab'])) eq(self.glob('aa[ab]'), map(self.norm, ['aaa', 'aab'])) eq(self.glob('*q'), []) @@ -87,23 +109,68 @@ eq(self.glob('*', '*a'), []) eq(self.glob('a', '*', '*', '*a'), [self.norm('a', 'bcd', 'efg', 'ha')]) - eq(self.glob('?a?', '*F'), map(self.norm, [os.path.join('aaa', 'zzzF'), - os.path.join('aab', 'F')])) + eq(self.glob('?a?', '*F'), [self.norm('aaa', 'zzzF'), + self.norm('aab', 'F')]) def test_glob_directory_with_trailing_slash(self): - # We are verifying that when there is wildcard pattern which - # ends with os.sep doesn't blow up. - res = glob.glob(self.tempdir + '*' + os.sep) - self.assertEqual(len(res), 1) - # either of these results are reasonable - self.assertIn(res[0], [self.tempdir, self.tempdir + os.sep]) + # Patterns ending with a slash shouldn't match non-dirs + res = glob.glob(self.norm('Z*Z') + os.sep) + self.assertEqual(res, []) + res = glob.glob(self.norm('ZZZ') + os.sep) + self.assertEqual(res, []) + # When there is a wildcard pattern which ends with os.sep, glob() + # doesn't blow up. + res = glob.glob(self.norm('aa*') + os.sep) + self.assertEqual(len(res), 2) + # either of these results is reasonable + self.assertIn(set(res), [ + {self.norm('aaa'), self.norm('aab')}, + {self.norm('aaa') + os.sep, self.norm('aab') + os.sep}, + ]) + def test_glob_unicode_directory_with_trailing_slash(self): + # Same as test_glob_directory_with_trailing_slash, but with an + # unicode argument. + res = glob.glob(fsdecode(self.norm('Z*Z') + os.sep)) + self.assertEqual(res, []) + res = glob.glob(fsdecode(self.norm('ZZZ') + os.sep)) + self.assertEqual(res, []) + res = glob.glob(fsdecode(self.norm('aa*') + os.sep)) + self.assertEqual(len(res), 2) + # either of these results is reasonable + self.assertIn(set(res), [ + {fsdecode(self.norm('aaa')), fsdecode(self.norm('aab'))}, + {fsdecode(self.norm('aaa') + os.sep), + fsdecode(self.norm('aab') + os.sep)}, + ]) + + @unittest.skipUnless(hasattr(os, 'symlink'), "Requires symlink support") + def test_glob_symlinks(self): + eq = self.assertSequencesEqual_noorder + eq(self.glob('sym3'), [self.norm('sym3')]) + eq(self.glob('sym3', '*'), [self.norm('sym3', 'EF'), + self.norm('sym3', 'efg')]) + self.assertIn(self.glob('sym3' + os.sep), + [[self.norm('sym3')], [self.norm('sym3') + os.sep]]) + eq(self.glob('*', '*F'), + [self.norm('aaa', 'zzzF'), self.norm('aab', 'F'), + self.norm('sym3', 'EF')]) + + @unittest.skipUnless(hasattr(os, 'symlink'), "Requires symlink support") def test_glob_broken_symlinks(self): - if hasattr(os, 'symlink'): - eq = self.assertSequencesEqual_noorder - eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2')]) - eq(self.glob('sym1'), [self.norm('sym1')]) - eq(self.glob('sym2'), [self.norm('sym2')]) + eq = self.assertSequencesEqual_noorder + eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2'), + self.norm('sym3')]) + eq(self.glob('sym1'), [self.norm('sym1')]) + eq(self.glob('sym2'), [self.norm('sym2')]) + + @unittest.skipUnless(sys.platform == "win32", "Win32 specific test") + def test_glob_magic_in_drive(self): + eq = self.assertSequencesEqual_noorder + eq(glob.glob('*:'), []) + eq(glob.glob(u'*:'), []) + eq(glob.glob('?:'), []) + eq(glob.glob(u'?:'), []) def test_main(): diff --git a/lib-python/2.7/test/test_gzip.py b/lib-python/2.7/test/test_gzip.py --- a/lib-python/2.7/test/test_gzip.py +++ b/lib-python/2.7/test/test_gzip.py @@ -53,6 +53,13 @@ d = f.read() self.assertEqual(d, data1*50) + def test_read_universal_newlines(self): + # Issue #5148: Reading breaks when mode contains 'U'. + self.test_write() + with gzip.GzipFile(self.filename, 'rU') as f: + d = f.read() + self.assertEqual(d, data1*50) + def test_io_on_closed_object(self): # Test that I/O operations on closed GzipFile objects raise a # ValueError, just like the corresponding functions on file objects. @@ -282,6 +289,24 @@ with gzip.GzipFile(fileobj=f, mode="w") as g: self.assertEqual(g.name, "") + def test_read_truncated(self): + data = data1*50 + buf = io.BytesIO() + with gzip.GzipFile(fileobj=buf, mode="w") as f: + f.write(data) + # Drop the CRC (4 bytes) and file size (4 bytes). + truncated = buf.getvalue()[:-8] + with gzip.GzipFile(fileobj=io.BytesIO(truncated)) as f: + self.assertRaises(EOFError, f.read) + with gzip.GzipFile(fileobj=io.BytesIO(truncated)) as f: + self.assertEqual(f.read(len(data)), data) + self.assertRaises(EOFError, f.read, 1) + # Incomplete 10-byte header. + for i in range(2, 10): + with gzip.GzipFile(fileobj=io.BytesIO(truncated[:i])) as f: + self.assertRaises(EOFError, f.read, 1) + + def test_main(verbose=None): test_support.run_unittest(TestGzip) diff --git a/lib-python/2.7/test/test_hashlib.py b/lib-python/2.7/test/test_hashlib.py --- a/lib-python/2.7/test/test_hashlib.py +++ b/lib-python/2.7/test/test_hashlib.py @@ -108,12 +108,8 @@ _algo.islower()])) def test_unknown_hash(self): - try: - hashlib.new('spam spam spam spam spam') - except ValueError: - pass - else: - self.assertTrue(0 == "hashlib didn't reject bogus hash name") + self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam') + self.assertRaises(TypeError, hashlib.new, 1) def test_get_builtin_constructor(self): get_builtin_constructor = hashlib.__dict__[ @@ -132,6 +128,7 @@ sys.modules['_md5'] = _md5 else: del sys.modules['_md5'] + self.assertRaises(TypeError, get_builtin_constructor, 3) def test_hexdigest(self): for name in self.supported_hash_names: @@ -170,6 +167,21 @@ % (name, hash_object_constructor, computed, len(data), digest)) + def check_update(self, name, data, digest): + constructors = self.constructors_to_test[name] + # 2 is for hashlib.name(...) and hashlib.new(name, ...) + self.assertGreaterEqual(len(constructors), 2) + for hash_object_constructor in constructors: + h = hash_object_constructor() + h.update(data) + computed = h.hexdigest() + self.assertEqual( + computed, digest, + "Hash algorithm %s using %s when updated returned hexdigest" + " %r for %d byte input data that should have hashed to %r." + % (name, hash_object_constructor, + computed, len(data), digest)) + def check_unicode(self, algorithm_name): # Unicode objects are not allowed as input. expected = hashlib.new(algorithm_name, str(u'spam')).hexdigest() @@ -203,6 +215,15 @@ except OverflowError: pass # 32-bit arch + @precisionbigmemtest(size=_4G + 5, memuse=1) + def test_case_md5_huge_update(self, size): + if size == _4G + 5: + try: + self.check_update('md5', 'A'*size, + 'c9af2dff37468ce5dfee8f2cfc0a9c6d') + except OverflowError: + pass # 32-bit arch + @precisionbigmemtest(size=_4G - 1, memuse=1) def test_case_md5_uintmax(self, size): if size == _4G - 1: @@ -231,6 +252,23 @@ self.check('sha1', "a" * 1000000, "34aa973cd4c4daa4f61eeb2bdbad27316534016f") + @precisionbigmemtest(size=_4G + 5, memuse=1) + def test_case_sha1_huge(self, size): + if size == _4G + 5: + try: + self.check('sha1', 'A'*size, + '87d745c50e6b2879ffa0fb2c930e9fbfe0dc9a5b') + except OverflowError: + pass # 32-bit arch + + @precisionbigmemtest(size=_4G + 5, memuse=1) + def test_case_sha1_huge_update(self, size): + if size == _4G + 5: + try: + self.check_update('sha1', 'A'*size, + '87d745c50e6b2879ffa0fb2c930e9fbfe0dc9a5b') + except OverflowError: + pass # 32-bit arch # use the examples from Federal Information Processing Standards # Publication 180-2, Secure Hash Standard, 2002 August 1 diff --git a/lib-python/2.7/test/test_heapq.py b/lib-python/2.7/test/test_heapq.py --- a/lib-python/2.7/test/test_heapq.py +++ b/lib-python/2.7/test/test_heapq.py @@ -315,6 +315,16 @@ 'Test multiple tiers of iterators' return chain(imap(lambda x:x, R(Ig(G(seqn))))) +class SideEffectLT: + def __init__(self, value, heap): + self.value = value + self.heap = heap + + def __lt__(self, other): + self.heap[:] = [] + return self.value < other.value + + class TestErrorHandling(TestCase): module = None @@ -361,6 +371,22 @@ self.assertRaises(TypeError, f, 2, N(s)) self.assertRaises(ZeroDivisionError, f, 2, E(s)) + # Issue #17278: the heap may change size while it's being walked. + + def test_heappush_mutating_heap(self): + heap = [] + heap.extend(SideEffectLT(i, heap) for i in range(200)) + # Python version raises IndexError, C version RuntimeError + with self.assertRaises((IndexError, RuntimeError)): + self.module.heappush(heap, SideEffectLT(5, heap)) + + def test_heappop_mutating_heap(self): + heap = [] + heap.extend(SideEffectLT(i, heap) for i in range(200)) + # Python version raises IndexError, C version RuntimeError + with self.assertRaises((IndexError, RuntimeError)): + self.module.heappop(heap) + class TestErrorHandlingPython(TestErrorHandling): module = py_heapq diff --git a/lib-python/2.7/test/test_htmlparser.py b/lib-python/2.7/test/test_htmlparser.py --- a/lib-python/2.7/test/test_htmlparser.py +++ b/lib-python/2.7/test/test_htmlparser.py @@ -260,6 +260,16 @@ ('starttag', 'a', [('foo', None), ('=', None), ('bar', None)]) ] self._run_check(html, expected) + #see issue #14538 + html = ('' + '') + expected = [ + ('starttag', 'meta', []), ('starttag', 'meta', []), + ('starttag', 'meta', []), ('starttag', 'meta', []), + ('startendtag', 'meta', []), ('startendtag', 'meta', []), + ('startendtag', 'meta', []), ('startendtag', 'meta', []), + ] + self._run_check(html, expected) def test_declaration_junk_chars(self): self._run_check("", [('decl', 'DOCTYPE foo $ ')]) diff --git a/lib-python/2.7/test/test_httplib.py b/lib-python/2.7/test/test_httplib.py --- a/lib-python/2.7/test/test_httplib.py +++ b/lib-python/2.7/test/test_httplib.py @@ -90,6 +90,34 @@ conn.request('POST', '/', body, headers) self.assertEqual(conn._buffer.count[header.lower()], 1) + def test_content_length_0(self): + + class ContentLengthChecker(list): + def __init__(self): + list.__init__(self) + self.content_length = None + def append(self, item): + kv = item.split(':', 1) + if len(kv) > 1 and kv[0].lower() == 'content-length': + self.content_length = kv[1].strip() + list.append(self, item) + + # POST with empty body + conn = httplib.HTTPConnection('example.com') + conn.sock = FakeSocket(None) + conn._buffer = ContentLengthChecker() + conn.request('POST', '/', '') + self.assertEqual(conn._buffer.content_length, '0', + 'Header Content-Length not set') + + # PUT request with empty body + conn = httplib.HTTPConnection('example.com') + conn.sock = FakeSocket(None) + conn._buffer = ContentLengthChecker() + conn.request('PUT', '/', '') + self.assertEqual(conn._buffer.content_length, '0', + 'Header Content-Length not set') + def test_putheader(self): conn = httplib.HTTPConnection('example.com') conn.sock = FakeSocket(None) @@ -138,7 +166,7 @@ self.assertEqual(repr(exc), '''BadStatusLine("\'\'",)''') def test_partial_reads(self): - # if we have a lenght, the system knows when to close itself + # if we have a length, the system knows when to close itself # same behaviour than when we read the whole thing with read() body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText" sock = FakeSocket(body) @@ -149,6 +177,32 @@ self.assertEqual(resp.read(2), 'xt') self.assertTrue(resp.isclosed()) + def test_partial_reads_no_content_length(self): + # when no length is present, the socket should be gracefully closed when + # all data was read + body = "HTTP/1.1 200 Ok\r\n\r\nText" + sock = FakeSocket(body) + resp = httplib.HTTPResponse(sock) + resp.begin() + self.assertEqual(resp.read(2), 'Te') + self.assertFalse(resp.isclosed()) + self.assertEqual(resp.read(2), 'xt') + self.assertEqual(resp.read(1), '') + self.assertTrue(resp.isclosed()) + + def test_partial_reads_incomplete_body(self): + # if the server shuts down the connection before the whole + # content-length is delivered, the socket is gracefully closed + body = "HTTP/1.1 200 Ok\r\nContent-Length: 10\r\n\r\nText" + sock = FakeSocket(body) + resp = httplib.HTTPResponse(sock) + resp.begin() + self.assertEqual(resp.read(2), 'Te') + self.assertFalse(resp.isclosed()) + self.assertEqual(resp.read(2), 'xt') + self.assertEqual(resp.read(1), '') + self.assertTrue(resp.isclosed()) + def test_host_port(self): # Check invalid host_port @@ -279,7 +333,7 @@ resp = httplib.HTTPResponse(sock, method="GET") resp.begin() self.assertEqual(resp.read(), 'Hello\r\n') - resp.close() + self.assertTrue(resp.isclosed()) def test_incomplete_read(self): sock = FakeSocket('HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\nHello\r\n') @@ -293,10 +347,9 @@ "IncompleteRead(7 bytes read, 3 more expected)") self.assertEqual(str(i), "IncompleteRead(7 bytes read, 3 more expected)") + self.assertTrue(resp.isclosed()) else: self.fail('IncompleteRead expected') - finally: - resp.close() def test_epipe(self): sock = EPipeSocket( @@ -349,6 +402,14 @@ resp.begin() self.assertRaises(httplib.LineTooLong, resp.read) + def test_early_eof(self): + # Test httpresponse with no \r\n termination, + body = "HTTP/1.1 200 Ok" + sock = FakeSocket(body) + resp = httplib.HTTPResponse(sock) + resp.begin() + self.assertEqual(resp.read(), '') + self.assertTrue(resp.isclosed()) class OfflineTest(TestCase): def test_responses(self): diff --git a/lib-python/2.7/test/test_httpservers.py b/lib-python/2.7/test/test_httpservers.py --- a/lib-python/2.7/test/test_httpservers.py +++ b/lib-python/2.7/test/test_httpservers.py @@ -4,11 +4,6 @@ Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest. """ -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -from SimpleHTTPServer import SimpleHTTPRequestHandler -from CGIHTTPServer import CGIHTTPRequestHandler -import CGIHTTPServer - import os import sys import re @@ -17,12 +12,17 @@ import urllib import httplib import tempfile +import unittest +import CGIHTTPServer -import unittest +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from SimpleHTTPServer import SimpleHTTPRequestHandler +from CGIHTTPServer import CGIHTTPRequestHandler from StringIO import StringIO +from test import test_support -from test import test_support + threading = test_support.import_module('threading') @@ -43,7 +43,7 @@ self.end_headers() self.wfile.write(b'Data\r\n') - def log_message(self, format, *args): + def log_message(self, fmt, *args): pass @@ -97,9 +97,9 @@ self.handler = SocketlessRequestHandler() def send_typical_request(self, message): - input = StringIO(message) + input_msg = StringIO(message) output = StringIO() - self.handler.rfile = input + self.handler.rfile = input_msg self.handler.wfile = output self.handler.handle_one_request() output.seek(0) @@ -296,7 +296,7 @@ os.chdir(self.cwd) try: shutil.rmtree(self.tempdir) - except: + except OSError: pass finally: BaseTestCase.tearDown(self) @@ -418,42 +418,44 @@ finally: BaseTestCase.tearDown(self) - def test_url_collapse_path_split(self): + def test_url_collapse_path(self): + # verify tail is the last portion and head is the rest on proper urls test_vectors = { - '': ('/', ''), + '': '//', '..': IndexError, '/.//..': IndexError, - '/': ('/', ''), - '//': ('/', ''), - '/\\': ('/', '\\'), - '/.//': ('/', ''), - 'cgi-bin/file1.py': ('/cgi-bin', 'file1.py'), - '/cgi-bin/file1.py': ('/cgi-bin', 'file1.py'), - '/cgi-bin/file1.py/PATH-INFO': ('/cgi-bin', 'file1.py/PATH-INFO'), - 'a': ('/', 'a'), - '/a': ('/', 'a'), - '//a': ('/', 'a'), - './a': ('/', 'a'), - './C:/': ('/C:', ''), - '/a/b': ('/a', 'b'), - '/a/b/': ('/a/b', ''), - '/a/b/c/..': ('/a/b', ''), - '/a/b/c/../d': ('/a/b', 'd'), - '/a/b/c/../d/e/../f': ('/a/b/d', 'f'), - '/a/b/c/../d/e/../../f': ('/a/b', 'f'), - '/a/b/c/../d/e/.././././..//f': ('/a/b', 'f'), + '/': '//', + '//': '//', + '/\\': '//\\', + '/.//': '//', + 'cgi-bin/file1.py': '/cgi-bin/file1.py', + '/cgi-bin/file1.py': '/cgi-bin/file1.py', + 'a': '//a', + '/a': '//a', + '//a': '//a', + './a': '//a', + './C:/': '/C:/', + '/a/b': '/a/b', + '/a/b/': '/a/b/', + '/a/b/.': '/a/b/', + '/a/b/c/..': '/a/b/', + '/a/b/c/../d': '/a/b/d', + '/a/b/c/../d/e/../f': '/a/b/d/f', + '/a/b/c/../d/e/../../f': '/a/b/f', + '/a/b/c/../d/e/.././././..//f': '/a/b/f', '../a/b/c/../d/e/.././././..//f': IndexError, - '/a/b/c/../d/e/../../../f': ('/a', 'f'), - '/a/b/c/../d/e/../../../../f': ('/', 'f'), + '/a/b/c/../d/e/../../../f': '/a/f', + '/a/b/c/../d/e/../../../../f': '//f', '/a/b/c/../d/e/../../../../../f': IndexError, - '/a/b/c/../d/e/../../../../f/..': ('/', ''), + '/a/b/c/../d/e/../../../../f/..': '//', + '/a/b/c/../d/e/../../../../f/../.': '//', } for path, expected in test_vectors.iteritems(): if isinstance(expected, type) and issubclass(expected, Exception): self.assertRaises(expected, - CGIHTTPServer._url_collapse_path_split, path) + CGIHTTPServer._url_collapse_path, path) else: - actual = CGIHTTPServer._url_collapse_path_split(path) + actual = CGIHTTPServer._url_collapse_path(path) self.assertEqual(expected, actual, msg='path = %r\nGot: %r\nWanted: %r' % (path, actual, expected)) diff --git a/lib-python/2.7/test/test_imaplib.py b/lib-python/2.7/test/test_imaplib.py --- a/lib-python/2.7/test/test_imaplib.py +++ b/lib-python/2.7/test/test_imaplib.py @@ -79,7 +79,7 @@ return line += part except IOError: - # ..but SSLSockets throw exceptions. + # ..but SSLSockets raise exceptions. return if line.endswith('\r\n'): break diff --git a/lib-python/2.7/test/test_import.py b/lib-python/2.7/test/test_import.py --- a/lib-python/2.7/test/test_import.py +++ b/lib-python/2.7/test/test_import.py @@ -5,6 +5,7 @@ import py_compile import random import stat +import struct import sys import unittest import textwrap @@ -15,12 +16,23 @@ from test import symlink_support from test import script_helper +def _files(name): + return (name + os.extsep + "py", + name + os.extsep + "pyc", + name + os.extsep + "pyo", + name + os.extsep + "pyw", + name + "$py.class") + +def chmod_files(name): + for f in _files(name): + try: + os.chmod(f, 0600) + except OSError as exc: + if exc.errno != errno.ENOENT: + raise + def remove_files(name): - for f in (name + os.extsep + "py", - name + os.extsep + "pyc", - name + os.extsep + "pyo", - name + os.extsep + "pyw", - name + "$py.class"): + for f in _files(name): unlink(f) @@ -120,6 +132,40 @@ unload(TESTFN) del sys.path[0] + def test_rewrite_pyc_with_read_only_source(self): + # Issue 6074: a long time ago on posix, and more recently on Windows, + # a read only source file resulted in a read only pyc file, which + # led to problems with updating it later + sys.path.insert(0, os.curdir) + fname = TESTFN + os.extsep + "py" + try: + # Write a Python file, make it read-only and import it + with open(fname, 'w') as f: + f.write("x = 'original'\n") + # Tweak the mtime of the source to ensure pyc gets updated later + s = os.stat(fname) + os.utime(fname, (s.st_atime, s.st_mtime-100000000)) + os.chmod(fname, 0400) + m1 = __import__(TESTFN) + self.assertEqual(m1.x, 'original') + # Change the file and then reimport it + os.chmod(fname, 0600) + with open(fname, 'w') as f: + f.write("x = 'rewritten'\n") + unload(TESTFN) + m2 = __import__(TESTFN) + self.assertEqual(m2.x, 'rewritten') + # Now delete the source file and check the pyc was rewritten + unlink(fname) + unload(TESTFN) + m3 = __import__(TESTFN) + self.assertEqual(m3.x, 'rewritten') + finally: + chmod_files(TESTFN) + remove_files(TESTFN) + unload(TESTFN) + del sys.path[0] + def test_imp_module(self): # Verify that the imp module can correctly load and find .py files @@ -305,6 +351,46 @@ del sys.path[0] remove_files(TESTFN) + def test_pyc_mtime(self): + # Test for issue #13863: .pyc timestamp sometimes incorrect on Windows. + sys.path.insert(0, os.curdir) + try: + # Jan 1, 2012; Jul 1, 2012. + mtimes = 1325376000, 1341100800 + + # Different names to avoid running into import caching. + tails = "spam", "eggs" + for mtime, tail in zip(mtimes, tails): + module = TESTFN + tail + source = module + ".py" + compiled = source + ('c' if __debug__ else 'o') + + # Create a new Python file with the given mtime. + with open(source, 'w') as f: + f.write("# Just testing\nx=1, 2, 3\n") + os.utime(source, (mtime, mtime)) + + # Generate the .pyc/o file; if it couldn't be created + # for some reason, skip the test. + m = __import__(module) + if not os.path.exists(compiled): + unlink(source) + self.skipTest("Couldn't create .pyc/.pyo file.") + + # Actual modification time of .py file. + mtime1 = int(os.stat(source).st_mtime) & 0xffffffff + + # mtime that was encoded in the .pyc file. + with open(compiled, 'rb') as f: + mtime2 = struct.unpack('= previous_groups[0]), + self.assertGreaterEqual(int(groups[0]), int(previous_groups[0]), "Non-monotonic seconds: '%s' before '%s'" % (previous_groups[0], groups[0])) - self.assertTrue(int(groups[1] >= previous_groups[1]) or - groups[0] != groups[1], - "Non-monotonic milliseconds: '%s' before '%s'" % - (previous_groups[1], groups[1])) + if int(groups[0]) == int(previous_groups[0]): + self.assertGreaterEqual(int(groups[1]), int(previous_groups[1]), + "Non-monotonic milliseconds: '%s' before '%s'" % + (previous_groups[1], groups[1])) self.assertTrue(int(groups[2]) == pid, "Process ID mismatch: '%s' should be '%s'" % (groups[2], pid)) @@ -813,7 +844,49 @@ self._box._refresh() self.assertTrue(refreshed()) -class _TestMboxMMDF(TestMailbox): + +class _TestSingleFile(TestMailbox): + '''Common tests for single-file mailboxes''' + + def test_add_doesnt_rewrite(self): + # When only adding messages, flush() should not rewrite the + # mailbox file. See issue #9559. + + # Inode number changes if the contents are written to another + # file which is then renamed over the original file. So we + # must check that the inode number doesn't change. + inode_before = os.stat(self._path).st_ino + + self._box.add(self._template % 0) + self._box.flush() + + inode_after = os.stat(self._path).st_ino + self.assertEqual(inode_before, inode_after) + + # Make sure the message was really added + self._box.close() + self._box = self._factory(self._path) + self.assertEqual(len(self._box), 1) + + def test_permissions_after_flush(self): + # See issue #5346 + + # Make the mailbox world writable. It's unlikely that the new + # mailbox file would have these permissions after flush(), + # because umask usually prevents it. + mode = os.stat(self._path).st_mode | 0o666 + os.chmod(self._path, mode) + + self._box.add(self._template % 0) + i = self._box.add(self._template % 1) + # Need to remove one message to make flush() create a new file + self._box.remove(i) + self._box.flush() + + self.assertEqual(os.stat(self._path).st_mode, mode) + + +class _TestMboxMMDF(_TestSingleFile): def tearDown(self): self._box.close() @@ -823,14 +896,14 @@ def test_add_from_string(self): # Add a string starting with 'From ' to the mailbox - key = self._box.add('From foo at bar blah\nFrom: foo\n\n0') + key = self._box.add('From foo at bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo at bar blah') - self.assertEqual(self._box[key].get_payload(), '0') + self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_mbox_or_mmdf_message(self): # Add an mboxMessage or MMDFMessage for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): - msg = class_('From foo at bar blah\nFrom: foo\n\n0') + msg = class_('From foo at bar blah\nFrom: foo\n\n0\n') key = self._box.add(msg) def test_open_close_open(self): @@ -914,7 +987,7 @@ self._box.close() -class TestMbox(_TestMboxMMDF): +class TestMbox(_TestMboxMMDF, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.mbox(path, factory) @@ -937,12 +1010,35 @@ perms = st.st_mode self.assertFalse((perms & 0111)) # Execute bits should all be off. -class TestMMDF(_TestMboxMMDF): + def test_terminating_newline(self): + message = email.message.Message() + message['From'] = 'john at example.com' + message.set_payload('No newline at the end') + i = self._box.add(message) + + # A newline should have been appended to the payload + message = self._box.get(i) + self.assertEqual(message.get_payload(), 'No newline at the end\n') + + def test_message_separator(self): + # Check there's always a single blank line after each message + self._box.add('From: foo\n\n0') # No newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + self._box.add('From: foo\n\n0\n') # Newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + +class TestMMDF(_TestMboxMMDF, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.MMDF(path, factory) -class TestMH(TestMailbox): +class TestMH(TestMailbox, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.MH(path, factory) @@ -1074,7 +1170,7 @@ return os.path.join(self._path, '.mh_sequences.lock') -class TestBabyl(TestMailbox): +class TestBabyl(_TestSingleFile, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.Babyl(path, factory) @@ -1103,7 +1199,7 @@ self.assertEqual(set(self._box.get_labels()), set(['blah'])) -class TestMessage(TestBase): +class TestMessage(TestBase, unittest.TestCase): _factory = mailbox.Message # Overridden by subclasses to reuse tests @@ -1174,7 +1270,7 @@ pass -class TestMaildirMessage(TestMessage): +class TestMaildirMessage(TestMessage, unittest.TestCase): _factory = mailbox.MaildirMessage @@ -1249,7 +1345,7 @@ self._check_sample(msg) -class _TestMboxMMDFMessage(TestMessage): +class _TestMboxMMDFMessage: _factory = mailbox._mboxMMDFMessage @@ -1296,12 +1392,12 @@ r"\d{2} \d{4}", msg.get_from())) -class TestMboxMessage(_TestMboxMMDFMessage): +class TestMboxMessage(_TestMboxMMDFMessage, TestMessage): _factory = mailbox.mboxMessage -class TestMHMessage(TestMessage): +class TestMHMessage(TestMessage, unittest.TestCase): _factory = mailbox.MHMessage @@ -1332,7 +1428,7 @@ self.assertEqual(msg.get_sequences(), ['foobar', 'replied']) -class TestBabylMessage(TestMessage): +class TestBabylMessage(TestMessage, unittest.TestCase): _factory = mailbox.BabylMessage @@ -1387,12 +1483,12 @@ self.assertEqual(visible[header], msg[header]) -class TestMMDFMessage(_TestMboxMMDFMessage): +class TestMMDFMessage(_TestMboxMMDFMessage, TestMessage): _factory = mailbox.MMDFMessage -class TestMessageConversion(TestBase): +class TestMessageConversion(TestBase, unittest.TestCase): def test_plain_to_x(self): # Convert Message to all formats @@ -1715,7 +1811,7 @@ proxy.close() -class TestProxyFile(TestProxyFileBase): +class TestProxyFile(TestProxyFileBase, unittest.TestCase): def setUp(self): self._path = test_support.TESTFN @@ -1764,7 +1860,7 @@ self._test_close(mailbox._ProxyFile(self._file)) -class TestPartialFile(TestProxyFileBase): +class TestPartialFile(TestProxyFileBase, unittest.TestCase): def setUp(self): self._path = test_support.TESTFN @@ -1831,6 +1927,10 @@ def setUp(self): # create a new maildir mailbox to work with: self._dir = test_support.TESTFN + if os.path.isdir(self._dir): + test_support.rmtree(self._dir) + if os.path.isfile(self._dir): + test_support.unlink(self._dir) os.mkdir(self._dir) os.mkdir(os.path.join(self._dir, "cur")) os.mkdir(os.path.join(self._dir, "tmp")) @@ -1840,10 +1940,10 @@ def tearDown(self): map(os.unlink, self._msgfiles) - os.rmdir(os.path.join(self._dir, "cur")) - os.rmdir(os.path.join(self._dir, "tmp")) - os.rmdir(os.path.join(self._dir, "new")) - os.rmdir(self._dir) + test_support.rmdir(os.path.join(self._dir, "cur")) + test_support.rmdir(os.path.join(self._dir, "tmp")) + test_support.rmdir(os.path.join(self._dir, "new")) + test_support.rmdir(self._dir) def createMessage(self, dir, mbox=False): t = int(time.time() % 1000000) @@ -1879,7 +1979,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1887,7 +1989,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1896,8 +2000,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 2) - self.assertIsNot(self.mbox.next(), None) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1906,11 +2014,13 @@ import email.parser fname = self.createMessage("cur", True) n = 0 - for msg in mailbox.PortableUnixMailbox(open(fname), + fid = open(fname) + for msg in mailbox.PortableUnixMailbox(fid, email.parser.Parser().parse): n += 1 self.assertEqual(msg["subject"], "Simple Test") self.assertEqual(len(str(msg)), len(FROM_)+len(DUMMY_MESSAGE)) + fid.close() self.assertEqual(n, 1) ## End: classes from the original module (for backward compatibility). diff --git a/lib-python/2.7/test/test_marshal.py b/lib-python/2.7/test/test_marshal.py --- a/lib-python/2.7/test/test_marshal.py +++ b/lib-python/2.7/test/test_marshal.py @@ -269,6 +269,53 @@ invalid_string = 'l\x02\x00\x00\x00\x00\x00\x00\x00' self.assertRaises(ValueError, marshal.loads, invalid_string) +LARGE_SIZE = 2**31 +character_size = 4 if sys.maxunicode > 0xFFFF else 2 +pointer_size = 8 if sys.maxsize > 0xFFFFFFFF else 4 + + at unittest.skipIf(LARGE_SIZE > sys.maxsize, "test cannot run on 32-bit systems") +class LargeValuesTestCase(unittest.TestCase): + def check_unmarshallable(self, data): + f = open(test_support.TESTFN, 'wb') + self.addCleanup(test_support.unlink, test_support.TESTFN) + with f: + self.assertRaises(ValueError, marshal.dump, data, f) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, memuse=1, dry_run=False) + def test_string(self, size): + self.check_unmarshallable('x' * size) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=character_size, dry_run=False) + def test_unicode(self, size): + self.check_unmarshallable(u'x' * size) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=pointer_size, dry_run=False) + def test_tuple(self, size): + self.check_unmarshallable((None,) * size) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=pointer_size, dry_run=False) + def test_list(self, size): + self.check_unmarshallable([None] * size) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=pointer_size*12 + sys.getsizeof(LARGE_SIZE-1), + dry_run=False) + def test_set(self, size): + self.check_unmarshallable(set(range(size))) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=pointer_size*12 + sys.getsizeof(LARGE_SIZE-1), + dry_run=False) + def test_frozenset(self, size): + self.check_unmarshallable(frozenset(range(size))) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, memuse=1, dry_run=False) + def test_bytearray(self, size): + self.check_unmarshallable(bytearray(size)) + def test_main(): test_support.run_unittest(IntTestCase, @@ -277,7 +324,9 @@ CodeTestCase, ContainerTestCase, ExceptionTestCase, - BugsTestCase) + BugsTestCase, + LargeValuesTestCase, + ) if __name__ == "__main__": test_main() diff --git a/lib-python/2.7/test/test_memoryio.py b/lib-python/2.7/test/test_memoryio.py --- a/lib-python/2.7/test/test_memoryio.py +++ b/lib-python/2.7/test/test_memoryio.py @@ -328,9 +328,9 @@ self.assertEqual(memio.isatty(), False) self.assertEqual(memio.closed, False) memio.close() - self.assertEqual(memio.writable(), True) - self.assertEqual(memio.readable(), True) - self.assertEqual(memio.seekable(), True) + self.assertRaises(ValueError, memio.writable) + self.assertRaises(ValueError, memio.readable) + self.assertRaises(ValueError, memio.seekable) self.assertRaises(ValueError, memio.isatty) self.assertEqual(memio.closed, True) @@ -638,6 +638,16 @@ memio.close() self.assertRaises(ValueError, memio.__setstate__, (b"closed", 0, None)) + check_sizeof = support.check_sizeof + + @support.cpython_only + def test_sizeof(self): + basesize = support.calcobjsize(b'P2PP2P') + check = self.check_sizeof + self.assertEqual(object.__sizeof__(io.BytesIO()), basesize) + check(io.BytesIO(), basesize ) + check(io.BytesIO(b'a'), basesize + 1 + 1 ) + check(io.BytesIO(b'a' * 1000), basesize + 1000 + 1 ) class CStringIOTest(PyStringIOTest): ioclass = io.StringIO diff --git a/lib-python/2.7/test/test_minidom.py b/lib-python/2.7/test/test_minidom.py --- a/lib-python/2.7/test/test_minidom.py +++ b/lib-python/2.7/test/test_minidom.py @@ -1060,7 +1060,7 @@ '\xa4', "testEncodings - encoding EURO SIGN") - # Verify that character decoding errors throw exceptions instead + # Verify that character decoding errors raise exceptions instead # of crashing self.assertRaises(UnicodeDecodeError, parseString, 'Comment \xe7a va ? Tr\xe8s bien ?') diff --git a/lib-python/2.7/test/test_mmap.py b/lib-python/2.7/test/test_mmap.py --- a/lib-python/2.7/test/test_mmap.py +++ b/lib-python/2.7/test/test_mmap.py @@ -466,6 +466,19 @@ f.flush () return mmap.mmap (f.fileno(), 0) + def test_empty_file (self): + f = open (TESTFN, 'w+b') + f.close() + with open(TESTFN, "rb") as f : + try: + m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) + m.close() + self.fail("should not have been able to mmap empty file") + except ValueError as e: + self.assertEqual(e.message, "cannot mmap an empty file") + except: + self.fail("unexpected exception: " + str(e)) + def test_offset (self): f = open (TESTFN, 'w+b') @@ -669,6 +682,13 @@ def test_large_filesize(self): with self._make_test_file(0x17FFFFFFF, b" ") as f: + if sys.maxsize < 0x180000000: + # On 32 bit platforms the file is larger than sys.maxsize so + # mapping the whole file should fail -- Issue #16743 + with self.assertRaises(OverflowError): + mmap.mmap(f.fileno(), 0x180000000, access=mmap.ACCESS_READ) + with self.assertRaises(ValueError): + mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) m = mmap.mmap(f.fileno(), 0x10000, access=mmap.ACCESS_READ) try: self.assertEqual(m.size(), 0x180000000) diff --git a/lib-python/2.7/test/test_multiprocessing.py b/lib-python/2.7/test/test_multiprocessing.py --- a/lib-python/2.7/test/test_multiprocessing.py +++ b/lib-python/2.7/test/test_multiprocessing.py @@ -16,6 +16,7 @@ import random import logging import errno +import test.script_helper from test import test_support from StringIO import StringIO _multiprocessing = test_support.import_module('_multiprocessing') @@ -325,6 +326,36 @@ ] self.assertEqual(result, expected) + @classmethod + def _test_sys_exit(cls, reason, testfn): + sys.stderr = open(testfn, 'w') + sys.exit(reason) + + def test_sys_exit(self): + # See Issue 13854 + if self.TYPE == 'threads': + return + + testfn = test_support.TESTFN + self.addCleanup(test_support.unlink, testfn) + + for reason, code in (([1, 2, 3], 1), ('ignore this', 0)): + p = self.Process(target=self._test_sys_exit, args=(reason, testfn)) + p.daemon = True + p.start() + p.join(5) + self.assertEqual(p.exitcode, code) + + with open(testfn, 'r') as f: + self.assertEqual(f.read().rstrip(), str(reason)) + + for reason in (True, False, 8): + p = self.Process(target=sys.exit, args=(reason,)) + p.daemon = True + p.start() + p.join(5) + self.assertEqual(p.exitcode, reason) + # # # @@ -1152,6 +1183,36 @@ join() self.assertTrue(join.elapsed < 0.2) + def test_empty_iterable(self): + # See Issue 12157 + p = self.Pool(1) + + self.assertEqual(p.map(sqr, []), []) + self.assertEqual(list(p.imap(sqr, [])), []) + self.assertEqual(list(p.imap_unordered(sqr, [])), []) + self.assertEqual(p.map_async(sqr, []).get(), []) + + p.close() + p.join() + +def unpickleable_result(): + return lambda: 42 + +class _TestPoolWorkerErrors(BaseTestCase): + ALLOWED_TYPES = ('processes', ) + + def test_unpickleable_result(self): + from multiprocessing.pool import MaybeEncodingError + p = multiprocessing.Pool(2) + + # Make sure we don't lose pool processes because of encoding errors. + for iteration in range(20): + res = p.apply_async(unpickleable_result) + self.assertRaises(MaybeEncodingError, res.get) + + p.close() + p.join() + class _TestPoolWorkerLifetime(BaseTestCase): ALLOWED_TYPES = ('processes', ) @@ -1452,6 +1513,7 @@ self.assertTimingAlmostEqual(poll.elapsed, TIMEOUT1) conn.send(None) + time.sleep(.1) self.assertEqual(poll(TIMEOUT1), True) self.assertTimingAlmostEqual(poll.elapsed, 0) @@ -1651,6 +1713,23 @@ self.assertEqual(conn.recv(), 'hello') p.join() l.close() + + def test_issue14725(self): + l = self.connection.Listener() + p = self.Process(target=self._test, args=(l.address,)) + p.daemon = True + p.start() + time.sleep(1) + # On Windows the client process should by now have connected, + # written data and closed the pipe handle by now. This causes + # ConnectNamdedPipe() to fail with ERROR_NO_DATA. See Issue + # 14725. + conn = l.accept() + self.assertEqual(conn.recv(), 'hello') + conn.close() + p.join() + l.close() + # # Test of sending connection and socket objects between processes # @@ -2026,6 +2105,38 @@ # assert self.__handled # +# Check that Process.join() retries if os.waitpid() fails with EINTR +# + +class _TestPollEintr(BaseTestCase): + + ALLOWED_TYPES = ('processes',) + + @classmethod + def _killer(cls, pid): + time.sleep(0.5) + os.kill(pid, signal.SIGUSR1) + + @unittest.skipUnless(hasattr(signal, 'SIGUSR1'), 'requires SIGUSR1') + def test_poll_eintr(self): + got_signal = [False] + def record(*args): + got_signal[0] = True + pid = os.getpid() + oldhandler = signal.signal(signal.SIGUSR1, record) + try: + killer = self.Process(target=self._killer, args=(pid,)) + killer.start() + p = self.Process(target=time.sleep, args=(1,)) + p.start() + p.join() + self.assertTrue(got_signal[0]) + self.assertEqual(p.exitcode, 0) + killer.join() + finally: + signal.signal(signal.SIGUSR1, oldhandler) + +# # Test to verify handle verification, see issue 3321 # @@ -2078,7 +2189,7 @@ 'Queue', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event', 'Value', 'Array', 'RawValue', 'RawArray', 'current_process', 'active_children', 'Pipe', - 'connection', 'JoinableQueue' + 'connection', 'JoinableQueue', 'Pool' ))) testcases_processes = create_test_cases(ProcessesMixin, type='processes') @@ -2092,7 +2203,7 @@ locals().update(get_attributes(manager, ( 'Queue', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event', 'Value', 'Array', 'list', 'dict', - 'Namespace', 'JoinableQueue' + 'Namespace', 'JoinableQueue', 'Pool' ))) testcases_manager = create_test_cases(ManagerMixin, type='manager') @@ -2106,7 +2217,7 @@ 'Queue', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event', 'Value', 'Array', 'current_process', 'active_children', 'Pipe', 'connection', 'dict', 'list', - 'Namespace', 'JoinableQueue' + 'Namespace', 'JoinableQueue', 'Pool' ))) testcases_threads = create_test_cases(ThreadsMixin, type='threads') @@ -2238,8 +2349,62 @@ flike.flush() assert sio.getvalue() == 'foo' +# +# Test interaction with socket timeouts - see Issue #6056 +# + +class TestTimeouts(unittest.TestCase): + @classmethod + def _test_timeout(cls, child, address): + time.sleep(1) + child.send(123) + child.close() + conn = multiprocessing.connection.Client(address) + conn.send(456) + conn.close() + + def test_timeout(self): + old_timeout = socket.getdefaulttimeout() + try: + socket.setdefaulttimeout(0.1) + parent, child = multiprocessing.Pipe(duplex=True) + l = multiprocessing.connection.Listener(family='AF_INET') + p = multiprocessing.Process(target=self._test_timeout, + args=(child, l.address)) + p.start() + child.close() + self.assertEqual(parent.recv(), 123) + parent.close() + conn = l.accept() + self.assertEqual(conn.recv(), 456) + conn.close() + l.close() + p.join(10) + finally: + socket.setdefaulttimeout(old_timeout) + +# +# Test what happens with no "if __name__ == '__main__'" +# + +class TestNoForkBomb(unittest.TestCase): + def test_noforkbomb(self): + name = os.path.join(os.path.dirname(__file__), 'mp_fork_bomb.py') + if WIN32: + rc, out, err = test.script_helper.assert_python_failure(name) + self.assertEqual('', out.decode('ascii')) + self.assertIn('RuntimeError', err.decode('ascii')) + else: + rc, out, err = test.script_helper.assert_python_ok(name) + self.assertEqual('123', out.decode('ascii').rstrip()) + self.assertEqual('', err.decode('ascii')) + +# +# +# + testcases_other = [OtherTest, TestInvalidHandle, TestInitializers, - TestStdinBadfiledescriptor] + TestStdinBadfiledescriptor, TestTimeouts, TestNoForkBomb] # # diff --git a/lib-python/2.7/test/test_mutex.py b/lib-python/2.7/test/test_mutex.py --- a/lib-python/2.7/test/test_mutex.py +++ b/lib-python/2.7/test/test_mutex.py @@ -14,7 +14,7 @@ m.lock(called_by_mutex2, "eggs") def called_by_mutex2(some_data): - self.assertEquals(some_data, "eggs") + self.assertEqual(some_data, "eggs") self.assertTrue(m.test(), "mutex not held") self.assertTrue(ready_for_2, "called_by_mutex2 called too soon") diff --git a/lib-python/2.7/test/test_old_mailbox.py b/lib-python/2.7/test/test_old_mailbox.py --- a/lib-python/2.7/test/test_old_mailbox.py +++ b/lib-python/2.7/test/test_old_mailbox.py @@ -73,7 +73,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 1) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) @@ -81,7 +83,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 1) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) @@ -90,8 +94,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 2) - self.assertTrue(self.mbox.next() is not None) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) diff --git a/lib-python/2.7/test/test_optparse.py b/lib-python/2.7/test/test_optparse.py --- a/lib-python/2.7/test/test_optparse.py +++ b/lib-python/2.7/test/test_optparse.py @@ -769,6 +769,13 @@ self.assertParseFail(["-test"], "no such option: -e") + def test_add_option_accepts_unicode(self): + self.parser.add_option(u"-u", u"--unicode", action="store_true") + self.assertParseOK(["-u"], + {'a': None, 'boo': None, 'foo': None, 'unicode': True}, + []) + + class TestBool(BaseTest): def setUp(self): options = [make_option("-v", diff --git a/lib-python/2.7/test/test_os.py b/lib-python/2.7/test/test_os.py --- a/lib-python/2.7/test/test_os.py +++ b/lib-python/2.7/test/test_os.py @@ -214,33 +214,33 @@ try: result[200] - self.fail("No exception thrown") + self.fail("No exception raised") except IndexError: pass # Make sure that assignment fails try: result.st_mode = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except (AttributeError, TypeError): pass try: result.st_rdev = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except (AttributeError, TypeError): pass try: result.parrot = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except AttributeError: pass # Use the stat_result constructor with a too-short tuple. try: result2 = os.stat_result((10,)) - self.fail("No exception thrown") + self.fail("No exception raised") except TypeError: pass @@ -274,20 +274,20 @@ # Make sure that assignment really fails try: result.f_bfree = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except TypeError: pass try: result.parrot = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except AttributeError: pass # Use the constructor with a too-short tuple. try: result2 = os.statvfs_result((10,)) - self.fail("No exception thrown") + self.fail("No exception raised") except TypeError: pass diff --git a/lib-python/2.7/test/test_parser.py b/lib-python/2.7/test/test_parser.py --- a/lib-python/2.7/test/test_parser.py +++ b/lib-python/2.7/test/test_parser.py @@ -1,7 +1,8 @@ import parser import unittest import sys -from test import test_support +import struct +from test import test_support as support # # First, we test that we can generate trees from valid source fragments, @@ -566,6 +567,17 @@ st = parser.suite('a = u"\u1"') self.assertRaises(SyntaxError, parser.compilest, st) + def test_issue_9011(self): + # Issue 9011: compilation of an unary minus expression changed + # the meaning of the ST, so that a second compilation produced + # incorrect results. + st = parser.expr('-3') + code1 = parser.compilest(st) + self.assertEqual(eval(code1), -3) + code2 = parser.compilest(st) + self.assertEqual(eval(code2), -3) + + class ParserStackLimitTestCase(unittest.TestCase): """try to push the parser to/over it's limits. see http://bugs.python.org/issue1881 for a discussion @@ -583,12 +595,57 @@ print >>sys.stderr, "Expecting 's_push: parser stack overflow' in next line" self.assertRaises(MemoryError, parser.expr, e) +class STObjectTestCase(unittest.TestCase): + """Test operations on ST objects themselves""" + + check_sizeof = support.check_sizeof + + @support.cpython_only + def test_sizeof(self): + def XXXROUNDUP(n): + if n <= 1: + return n + if n <= 128: + return (n + 3) & ~3 + return 1 << (n - 1).bit_length() + + basesize = support.calcobjsize('Pii') + nodesize = struct.calcsize('hP3iP0h') + def sizeofchildren(node): + if node is None: + return 0 + res = 0 + hasstr = len(node) > 1 and isinstance(node[-1], str) + if hasstr: + res += len(node[-1]) + 1 + children = node[1:-1] if hasstr else node[1:] + if children: + res += XXXROUNDUP(len(children)) * nodesize + for child in children: + res += sizeofchildren(child) + return res + + def check_st_sizeof(st): + self.check_sizeof(st, basesize + nodesize + + sizeofchildren(st.totuple())) + + check_st_sizeof(parser.expr('2 + 3')) + check_st_sizeof(parser.expr('2 + 3 + 4')) + check_st_sizeof(parser.suite('x = 2 + 3')) + check_st_sizeof(parser.suite('')) + check_st_sizeof(parser.suite('# -*- coding: utf-8 -*-')) + check_st_sizeof(parser.expr('[' + '2,' * 1000 + ']')) + + + # XXX tests for pickling and unpickling of ST objects should go here + def test_main(): - test_support.run_unittest( + support.run_unittest( RoundtripLegalSyntaxTestCase, IllegalSyntaxTestCase, CompileTestCase, ParserStackLimitTestCase, + STObjectTestCase, ) diff --git a/lib-python/2.7/test/test_pdb.py b/lib-python/2.7/test/test_pdb.py --- a/lib-python/2.7/test/test_pdb.py +++ b/lib-python/2.7/test/test_pdb.py @@ -6,12 +6,69 @@ import os import unittest import subprocess +import textwrap from test import test_support # This little helper class is essential for testing pdb under doctest. from test_doctest import _FakeInput +class PdbTestCase(unittest.TestCase): + + def run_pdb(self, script, commands): + """Run 'script' lines with pdb and the pdb 'commands'.""" + filename = 'main.py' + with open(filename, 'w') as f: + f.write(textwrap.dedent(script)) + self.addCleanup(test_support.unlink, filename) + cmd = [sys.executable, '-m', 'pdb', filename] + stdout = stderr = None + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + stdout, stderr = proc.communicate(commands) + proc.stdout.close() + proc.stdin.close() + return stdout, stderr + + def test_issue13183(self): + script = """ + from bar import bar + + def foo(): + bar() + + def nope(): + pass + + def foobar(): + foo() + nope() + + foobar() + """ + commands = """ + from bar import bar + break bar + continue + step + step + quit + """ + bar = """ + def bar(): + pass + """ + with open('bar.py', 'w') as f: + f.write(textwrap.dedent(bar)) + self.addCleanup(test_support.unlink, 'bar.py') + stdout, stderr = self.run_pdb(script, commands) + self.assertTrue( + any('main.py(5)foo()->None' in l for l in stdout.splitlines()), + 'Fail to step into the caller after a return') + + class PdbTestInput(object): """Context manager that makes testing Pdb in doctests easier.""" @@ -309,7 +366,9 @@ def test_main(): from test import test_pdb test_support.run_doctest(test_pdb, verbosity=True) - test_support.run_unittest(ModuleInitTester) + test_support.run_unittest( + PdbTestCase, + ModuleInitTester) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_peepholer.py b/lib-python/2.7/test/test_peepholer.py --- a/lib-python/2.7/test/test_peepholer.py +++ b/lib-python/2.7/test/test_peepholer.py @@ -138,21 +138,22 @@ self.assertIn('(1000)', asm) def test_binary_subscr_on_unicode(self): - # valid code get optimized + # unicode strings don't get optimized asm = dis_single('u"foo"[0]') - self.assertIn("(u'f')", asm) - self.assertNotIn('BINARY_SUBSCR', asm) + self.assertNotIn("(u'f')", asm) + self.assertIn('BINARY_SUBSCR', asm) asm = dis_single('u"\u0061\uffff"[1]') - self.assertIn("(u'\\uffff')", asm) - self.assertNotIn('BINARY_SUBSCR', asm) + self.assertNotIn("(u'\\uffff')", asm) + self.assertIn('BINARY_SUBSCR', asm) - # invalid code doesn't get optimized # out of range asm = dis_single('u"fuu"[10]') self.assertIn('BINARY_SUBSCR', asm) # non-BMP char (see #5057) asm = dis_single('u"\U00012345"[0]') self.assertIn('BINARY_SUBSCR', asm) + asm = dis_single('u"\U00012345abcdef"[3]') + self.assertIn('BINARY_SUBSCR', asm) def test_folding_of_unaryops_on_constants(self): diff --git a/lib-python/2.7/test/test_pickle.py b/lib-python/2.7/test/test_pickle.py --- a/lib-python/2.7/test/test_pickle.py +++ b/lib-python/2.7/test/test_pickle.py @@ -3,10 +3,11 @@ from test import test_support -from test.pickletester import AbstractPickleTests -from test.pickletester import AbstractPickleModuleTests -from test.pickletester import AbstractPersistentPicklerTests -from test.pickletester import AbstractPicklerUnpicklerObjectTests +from test.pickletester import (AbstractPickleTests, + AbstractPickleModuleTests, + AbstractPersistentPicklerTests, + AbstractPicklerUnpicklerObjectTests, + BigmemPickleTests) class PickleTests(AbstractPickleTests, AbstractPickleModuleTests): @@ -66,6 +67,16 @@ pickler_class = pickle.Pickler unpickler_class = pickle.Unpickler +class PickleBigmemPickleTests(BigmemPickleTests): + + def dumps(self, arg, proto=0, fast=0): + # Ignore fast + return pickle.dumps(arg, proto) + + def loads(self, buf): + # Ignore fast + return pickle.loads(buf) + def test_main(): test_support.run_unittest( @@ -73,6 +84,7 @@ PicklerTests, PersPicklerTests, PicklerUnpicklerObjectTests, + PickleBigmemPickleTests, ) test_support.run_doctest(pickle) diff --git a/lib-python/2.7/test/test_poll.py b/lib-python/2.7/test/test_poll.py --- a/lib-python/2.7/test/test_poll.py +++ b/lib-python/2.7/test/test_poll.py @@ -1,6 +1,7 @@ # Test case for the os.poll() function import os, select, random, unittest +import _testcapi from test.test_support import TESTFN, run_unittest try: @@ -150,6 +151,15 @@ if x != 5: self.fail('Overflow must have occurred') + pollster = select.poll() + # Issue 15989 + self.assertRaises(OverflowError, pollster.register, 0, + _testcapi.SHRT_MAX + 1) + self.assertRaises(OverflowError, pollster.register, 0, + _testcapi.USHRT_MAX + 1) + self.assertRaises(OverflowError, pollster.poll, _testcapi.INT_MAX + 1) + self.assertRaises(OverflowError, pollster.poll, _testcapi.UINT_MAX + 1) + def test_main(): run_unittest(PollTests) diff --git a/lib-python/2.7/test/test_posix.py b/lib-python/2.7/test/test_posix.py --- a/lib-python/2.7/test/test_posix.py +++ b/lib-python/2.7/test/test_posix.py @@ -9,6 +9,7 @@ import sys import time import os +import platform import pwd import shutil import stat @@ -107,7 +108,11 @@ # If a non-privileged user invokes it, it should fail with OSError # EPERM. if os.getuid() != 0: - name = pwd.getpwuid(posix.getuid()).pw_name + try: + name = pwd.getpwuid(posix.getuid()).pw_name + except KeyError: + # the current UID may not have a pwd entry + raise unittest.SkipTest("need a pwd entry") try: posix.initgroups(name, 13) except OSError as e: @@ -217,26 +222,64 @@ if hasattr(posix, 'stat'): self.assertTrue(posix.stat(test_support.TESTFN)) - def _test_all_chown_common(self, chown_func, first_param): + def _test_all_chown_common(self, chown_func, first_param, stat_func): """Common code for chown, fchown and lchown tests.""" - if os.getuid() == 0: - try: - # Many linux distros have a nfsnobody user as MAX_UID-2 - # that makes a good test case for signedness issues. - # http://bugs.python.org/issue1747858 - # This part of the test only runs when run as root. - # Only scary people run their tests as root. - ent = pwd.getpwnam('nfsnobody') - chown_func(first_param, ent.pw_uid, ent.pw_gid) - except KeyError: - pass + def check_stat(uid, gid): + if stat_func is not None: + stat = stat_func(first_param) + self.assertEqual(stat.st_uid, uid) + self.assertEqual(stat.st_gid, gid) + uid = os.getuid() + gid = os.getgid() + # test a successful chown call + chown_func(first_param, uid, gid) + check_stat(uid, gid) + chown_func(first_param, -1, gid) + check_stat(uid, gid) + chown_func(first_param, uid, -1) + check_stat(uid, gid) + + if uid == 0: + # Try an amusingly large uid/gid to make sure we handle + # large unsigned values. (chown lets you use any + # uid/gid you like, even if they aren't defined.) + # + # This problem keeps coming up: + # http://bugs.python.org/issue1747858 + # http://bugs.python.org/issue4591 + # http://bugs.python.org/issue15301 + # Hopefully the fix in 4591 fixes it for good! + # + # This part of the test only runs when run as root. + # Only scary people run their tests as root. + + big_value = 2**31 + chown_func(first_param, big_value, big_value) + check_stat(big_value, big_value) + chown_func(first_param, -1, -1) + check_stat(big_value, big_value) + chown_func(first_param, uid, gid) + check_stat(uid, gid) + elif platform.system() in ('HP-UX', 'SunOS'): + # HP-UX and Solaris can allow a non-root user to chown() to root + # (issue #5113) + raise unittest.SkipTest("Skipping because of non-standard chown() " + "behavior") else: # non-root cannot chown to root, raises OSError - self.assertRaises(OSError, chown_func, - first_param, 0, 0) - - # test a successful chown call - chown_func(first_param, os.getuid(), os.getgid()) + self.assertRaises(OSError, chown_func, first_param, 0, 0) + check_stat(uid, gid) + self.assertRaises(OSError, chown_func, first_param, 0, -1) + check_stat(uid, gid) + if 0 not in os.getgroups(): + self.assertRaises(OSError, chown_func, first_param, -1, 0) + check_stat(uid, gid) + # test illegal types + for t in str, float: + self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) + check_stat(uid, gid) + self.assertRaises(TypeError, chown_func, first_param, uid, t(gid)) + check_stat(uid, gid) @unittest.skipUnless(hasattr(posix, 'chown'), "test needs os.chown()") def test_chown(self): @@ -246,7 +289,8 @@ # re-create the file open(test_support.TESTFN, 'w').close() - self._test_all_chown_common(posix.chown, test_support.TESTFN) + self._test_all_chown_common(posix.chown, test_support.TESTFN, + getattr(posix, 'stat', None)) @unittest.skipUnless(hasattr(posix, 'fchown'), "test needs os.fchown()") def test_fchown(self): @@ -256,7 +300,8 @@ test_file = open(test_support.TESTFN, 'w') try: fd = test_file.fileno() - self._test_all_chown_common(posix.fchown, fd) + self._test_all_chown_common(posix.fchown, fd, + getattr(posix, 'fstat', None)) finally: test_file.close() @@ -265,7 +310,8 @@ os.unlink(test_support.TESTFN) # create a symlink os.symlink(_DUMMY_SYMLINK, test_support.TESTFN) - self._test_all_chown_common(posix.lchown, test_support.TESTFN) + self._test_all_chown_common(posix.lchown, test_support.TESTFN, + getattr(posix, 'lstat', None)) def test_chdir(self): if hasattr(posix, 'chdir'): @@ -324,7 +370,16 @@ def _test_chflags_regular_file(self, chflags_func, target_file): st = os.stat(target_file) self.assertTrue(hasattr(st, 'st_flags')) - chflags_func(target_file, st.st_flags | stat.UF_IMMUTABLE) + + # ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE. + try: + chflags_func(target_file, st.st_flags | stat.UF_IMMUTABLE) + except OSError as err: + if err.errno != errno.EOPNOTSUPP: + raise + msg = 'chflag UF_IMMUTABLE not supported by underlying fs' + self.skipTest(msg) + try: new_st = os.stat(target_file) self.assertEqual(st.st_flags | stat.UF_IMMUTABLE, new_st.st_flags) @@ -353,8 +408,16 @@ self.teardown_files.append(_DUMMY_SYMLINK) dummy_symlink_st = os.lstat(_DUMMY_SYMLINK) - posix.lchflags(_DUMMY_SYMLINK, - dummy_symlink_st.st_flags | stat.UF_IMMUTABLE) + # ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE. + try: + posix.lchflags(_DUMMY_SYMLINK, + dummy_symlink_st.st_flags | stat.UF_IMMUTABLE) + except OSError as err: + if err.errno != errno.EOPNOTSUPP: + raise + msg = 'chflag UF_IMMUTABLE not supported by underlying fs' + self.skipTest(msg) + try: new_testfn_st = os.stat(test_support.TESTFN) new_dummy_symlink_st = os.lstat(_DUMMY_SYMLINK) @@ -395,8 +458,16 @@ _create_and_do_getcwd(dirname, current_path_length + len(dirname) + 1) except OSError as e: expected_errno = errno.ENAMETOOLONG - if 'sunos' in sys.platform or 'openbsd' in sys.platform: - expected_errno = errno.ERANGE # Issue 9185 + # The following platforms have quirky getcwd() + # behaviour -- see issue 9185 and 15765 for + # more information. + quirky_platform = ( + 'sunos' in sys.platform or + 'netbsd' in sys.platform or + 'openbsd' in sys.platform + ) + if quirky_platform: + expected_errno = errno.ERANGE self.assertEqual(e.errno, expected_errno) finally: os.chdir('..') @@ -412,10 +483,18 @@ def test_getgroups(self): with os.popen('id -G') as idg: groups = idg.read().strip() + ret = idg.close() - if not groups: + if ret != None or not groups: raise unittest.SkipTest("need working 'id -G'") + # Issues 16698: OS X ABIs prior to 10.6 have limits on getgroups() + if sys.platform == 'darwin': + import sysconfig + dt = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') or '10.0' + if float(dt) < 10.6: + raise unittest.SkipTest("getgroups(2) is broken prior to 10.6") + # 'id -G' and 'os.getgroups()' should return the same # groups, ignoring order and duplicates. # #10822 - it is implementation defined whether posix.getgroups() diff --git a/lib-python/2.7/test/test_posixpath.py b/lib-python/2.7/test/test_posixpath.py --- a/lib-python/2.7/test/test_posixpath.py +++ b/lib-python/2.7/test/test_posixpath.py @@ -9,6 +9,16 @@ ABSTFN = abspath(test_support.TESTFN) +def skip_if_ABSTFN_contains_backslash(test): + """ + On Windows, posixpath.abspath still returns paths with backslashes + instead of posix forward slashes. If this is the case, several tests + fail, so skip them. + """ + found_backslash = '\\' in ABSTFN + msg = "ABSTFN is not a posix path - tests fail" + return [test, unittest.skip(msg)(test)][found_backslash] + def safe_rmdir(dirname): try: os.rmdir(dirname) @@ -110,8 +120,10 @@ ), True ) - # If we don't have links, assume that os.stat doesn't return resonable - # inode information and thus, that samefile() doesn't work + + # If we don't have links, assume that os.stat doesn't return + # reasonable inode information and thus, that samefile() doesn't + # work. if hasattr(os, "symlink"): os.symlink( test_support.TESTFN + "1", @@ -151,19 +163,19 @@ ), True ) - # If we don't have links, assume that os.stat() doesn't return resonable - # inode information and thus, that samefile() doesn't work + # If we don't have links, assume that os.stat() doesn't return + # reasonable inode information and thus, that samestat() doesn't + # work. if hasattr(os, "symlink"): - if hasattr(os, "symlink"): - os.symlink(test_support.TESTFN + "1", test_support.TESTFN + "2") - self.assertIs( - posixpath.samestat( - os.stat(test_support.TESTFN + "1"), - os.stat(test_support.TESTFN + "2") - ), - True - ) - os.remove(test_support.TESTFN + "2") + os.symlink(test_support.TESTFN + "1", test_support.TESTFN + "2") + self.assertIs( + posixpath.samestat( + os.stat(test_support.TESTFN + "1"), + os.stat(test_support.TESTFN + "2") + ), + True + ) + os.remove(test_support.TESTFN + "2") f = open(test_support.TESTFN + "2", "wb") f.write("bar") f.close() @@ -201,6 +213,7 @@ with test_support.EnvironmentVarGuard() as env: env['HOME'] = '/' self.assertEqual(posixpath.expanduser("~"), "/") + self.assertEqual(posixpath.expanduser("~/foo"), "/foo") def test_normpath(self): self.assertEqual(posixpath.normpath(""), ".") @@ -211,6 +224,18 @@ self.assertEqual(posixpath.normpath("///foo/.//bar//.//..//.//baz"), "/foo/baz") self.assertEqual(posixpath.normpath("///..//./foo/.//bar"), "/foo/bar") + @skip_if_ABSTFN_contains_backslash + def test_realpath_curdir(self): + self.assertEqual(realpath('.'), os.getcwd()) + self.assertEqual(realpath('./.'), os.getcwd()) + self.assertEqual(realpath('/'.join(['.'] * 100)), os.getcwd()) + + @skip_if_ABSTFN_contains_backslash + def test_realpath_pardir(self): + self.assertEqual(realpath('..'), dirname(os.getcwd())) + self.assertEqual(realpath('../..'), dirname(dirname(os.getcwd()))) + self.assertEqual(realpath('/'.join(['..'] * 100)), '/') + if hasattr(os, "symlink"): def test_realpath_basic(self): # Basic operation. @@ -233,6 +258,22 @@ self.assertEqual(realpath(ABSTFN+"1"), ABSTFN+"1") self.assertEqual(realpath(ABSTFN+"2"), ABSTFN+"2") + self.assertEqual(realpath(ABSTFN+"1/x"), ABSTFN+"1/x") + self.assertEqual(realpath(ABSTFN+"1/.."), dirname(ABSTFN)) + self.assertEqual(realpath(ABSTFN+"1/../x"), dirname(ABSTFN) + "/x") + os.symlink(ABSTFN+"x", ABSTFN+"y") + self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "y"), + ABSTFN + "y") + self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "1"), + ABSTFN + "1") + + os.symlink(basename(ABSTFN) + "a/b", ABSTFN+"a") + self.assertEqual(realpath(ABSTFN+"a"), ABSTFN+"a/b") + + os.symlink("../" + basename(dirname(ABSTFN)) + "/" + + basename(ABSTFN) + "c", ABSTFN+"c") + self.assertEqual(realpath(ABSTFN+"c"), ABSTFN+"c") + # Test using relative path as well. os.chdir(dirname(ABSTFN)) self.assertEqual(realpath(basename(ABSTFN)), ABSTFN) @@ -241,6 +282,40 @@ test_support.unlink(ABSTFN) test_support.unlink(ABSTFN+"1") test_support.unlink(ABSTFN+"2") + test_support.unlink(ABSTFN+"y") + test_support.unlink(ABSTFN+"c") + test_support.unlink(ABSTFN+"a") + + def test_realpath_repeated_indirect_symlinks(self): + # Issue #6975. + try: + os.mkdir(ABSTFN) + os.symlink('../' + basename(ABSTFN), ABSTFN + '/self') + os.symlink('self/self/self', ABSTFN + '/link') + self.assertEqual(realpath(ABSTFN + '/link'), ABSTFN) + finally: + test_support.unlink(ABSTFN + '/self') + test_support.unlink(ABSTFN + '/link') + safe_rmdir(ABSTFN) + + def test_realpath_deep_recursion(self): + depth = 10 + old_path = abspath('.') + try: + os.mkdir(ABSTFN) + for i in range(depth): + os.symlink('/'.join(['%d' % i] * 10), ABSTFN + '/%d' % (i + 1)) + os.symlink('.', ABSTFN + '/0') + self.assertEqual(realpath(ABSTFN + '/%d' % depth), ABSTFN) + + # Test using relative path as well. + os.chdir(ABSTFN) + self.assertEqual(realpath('%d' % depth), ABSTFN) + finally: + os.chdir(old_path) + for i in range(depth + 1): + test_support.unlink(ABSTFN + '/%d' % i) + safe_rmdir(ABSTFN) def test_realpath_resolve_parents(self): # We also need to resolve any symlinks in the parents of a relative diff --git a/lib-python/2.7/test/test_property.py b/lib-python/2.7/test/test_property.py --- a/lib-python/2.7/test/test_property.py +++ b/lib-python/2.7/test/test_property.py @@ -163,7 +163,7 @@ Foo.spam.__doc__, "spam wrapped in property subclass") - @unittest.skipIf(sys.flags.optimize <= 2, + @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_setter_copies_getter_docstring(self): class Foo(object): @@ -196,7 +196,7 @@ FooSub.spam.__doc__, "spam wrapped in property subclass") - @unittest.skipIf(sys.flags.optimize <= 2, + @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_new_getter_new_docstring(self): diff --git a/lib-python/2.7/test/test_pty.py b/lib-python/2.7/test/test_pty.py --- a/lib-python/2.7/test/test_pty.py +++ b/lib-python/2.7/test/test_pty.py @@ -152,7 +152,7 @@ # platform-dependent amount of data is written to its fd. On # Linux 2.6, it's 4000 bytes and the child won't block, but on OS # X even the small writes in the child above will block it. Also - # on Linux, the read() will throw an OSError (input/output error) + # on Linux, the read() will raise an OSError (input/output error) # when it tries to read past the end of the buffer but the child's # already exited, so catch and discard those exceptions. It's not # worth checking for EIO. diff --git a/lib-python/2.7/test/test_pwd.py b/lib-python/2.7/test/test_pwd.py --- a/lib-python/2.7/test/test_pwd.py +++ b/lib-python/2.7/test/test_pwd.py @@ -49,7 +49,9 @@ def test_errors(self): self.assertRaises(TypeError, pwd.getpwuid) + self.assertRaises(TypeError, pwd.getpwuid, 3.14) self.assertRaises(TypeError, pwd.getpwnam) + self.assertRaises(TypeError, pwd.getpwnam, 42) self.assertRaises(TypeError, pwd.getpwall, 42) # try to get some errors @@ -93,6 +95,13 @@ self.assertNotIn(fakeuid, byuids) self.assertRaises(KeyError, pwd.getpwuid, fakeuid) + # -1 shouldn't be a valid uid because it has a special meaning in many + # uid-related functions + self.assertRaises(KeyError, pwd.getpwuid, -1) + # should be out of uid_t range + self.assertRaises(KeyError, pwd.getpwuid, 2**128) + self.assertRaises(KeyError, pwd.getpwuid, -2**128) + def test_main(): test_support.run_unittest(PwdTest) diff --git a/lib-python/2.7/test/test_pyclbr.py b/lib-python/2.7/test/test_pyclbr.py --- a/lib-python/2.7/test/test_pyclbr.py +++ b/lib-python/2.7/test/test_pyclbr.py @@ -188,6 +188,11 @@ cm('email.parser') cm('test.test_pyclbr') + def test_issue_14798(self): + # test ImportError is raised when the first part of a dotted name is + # not a package + self.assertRaises(ImportError, pyclbr.readmodule_ex, 'asyncore.foo') + def test_main(): run_unittest(PyclbrTest) diff --git a/lib-python/2.7/test/test_pydoc.py b/lib-python/2.7/test/test_pydoc.py --- a/lib-python/2.7/test/test_pydoc.py +++ b/lib-python/2.7/test/test_pydoc.py @@ -16,6 +16,14 @@ from test import pydoc_mod +if test.test_support.HAVE_DOCSTRINGS: + expected_data_docstrings = ( + 'dictionary for instance variables (if defined)', + 'list of weak references to the object (if defined)', + ) +else: + expected_data_docstrings = ('', '') + expected_text_pattern = \ """ NAME @@ -40,11 +48,9 @@ class B(__builtin__.object) | Data descriptors defined here: |\x20\x20 - | __dict__ - | dictionary for instance variables (if defined) + | __dict__%s |\x20\x20 - | __weakref__ - | list of weak references to the object (if defined) + | __weakref__%s |\x20\x20 | ---------------------------------------------------------------------- | Data and other attributes defined here: @@ -75,6 +81,9 @@ Nobody """.strip() +expected_text_data_docstrings = tuple('\n | ' + s if s else '' + for s in expected_data_docstrings) + expected_html_pattern = \ """ @@ -121,10 +130,10 @@
     Data descriptors defined here:
__dict__
-
dictionary for instance variables (if defined)
+
%s
__weakref__
-
list of weak references to the object (if defined)
+
%s

Data and other attributes defined here:
@@ -168,6 +177,8 @@
Nobody
""".strip() +expected_html_data_docstrings = tuple(s.replace(' ', ' ') + for s in expected_data_docstrings) # output pattern for missing module missing_pattern = "no Python documentation found for '%s'" @@ -229,7 +240,9 @@ mod_url = nturl2path.pathname2url(mod_file) else: mod_url = mod_file - expected_html = expected_html_pattern % (mod_url, mod_file, doc_loc) + expected_html = expected_html_pattern % ( + (mod_url, mod_file, doc_loc) + + expected_html_data_docstrings) if result != expected_html: print_diffs(expected_html, result) self.fail("outputs are not equal, see diff above") @@ -238,8 +251,9 @@ "Docstrings are omitted with -O2 and above") def test_text_doc(self): result, doc_loc = get_pydoc_text(pydoc_mod) - expected_text = expected_text_pattern % \ - (inspect.getabsfile(pydoc_mod), doc_loc) + expected_text = expected_text_pattern % ( + (inspect.getabsfile(pydoc_mod), doc_loc) + + expected_text_data_docstrings) if result != expected_text: print_diffs(expected_text, result) self.fail("outputs are not equal, see diff above") @@ -249,6 +263,17 @@ result, doc_loc = get_pydoc_text(xml.etree) self.assertEqual(doc_loc, "", "MODULE DOCS incorrectly includes a link") + def test_non_str_name(self): + # issue14638 + # Treat illegal (non-str) name like no name + class A: + __name__ = 42 + class B: + pass + adoc = pydoc.render_doc(A()) + bdoc = pydoc.render_doc(B()) + self.assertEqual(adoc.replace("A", "B"), bdoc) + def test_not_here(self): missing_module = "test.i_am_not_here" result = run_pydoc(missing_module) diff --git a/lib-python/2.7/test/test_pyexpat.py b/lib-python/2.7/test/test_pyexpat.py --- a/lib-python/2.7/test/test_pyexpat.py +++ b/lib-python/2.7/test/test_pyexpat.py @@ -588,6 +588,58 @@ except expat.ExpatError as e: self.assertEqual(str(e), 'XML declaration not well-formed: line 1, column 14') +class ForeignDTDTests(unittest.TestCase): + """ + Tests for the UseForeignDTD method of expat parser objects. + """ + def test_use_foreign_dtd(self): + """ + If UseForeignDTD is passed True and a document without an external + entity reference is parsed, ExternalEntityRefHandler is first called + with None for the public and system ids. + """ + handler_call_args = [] + def resolve_entity(context, base, system_id, public_id): + handler_call_args.append((public_id, system_id)) + return 1 + + parser = expat.ParserCreate() + parser.UseForeignDTD(True) + parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_ALWAYS) + parser.ExternalEntityRefHandler = resolve_entity + parser.Parse("") + self.assertEqual(handler_call_args, [(None, None)]) + + # test UseForeignDTD() is equal to UseForeignDTD(True) + handler_call_args[:] = [] + + parser = expat.ParserCreate() + parser.UseForeignDTD() + parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_ALWAYS) + parser.ExternalEntityRefHandler = resolve_entity + parser.Parse("") + self.assertEqual(handler_call_args, [(None, None)]) + + def test_ignore_use_foreign_dtd(self): + """ + If UseForeignDTD is passed True and a document with an external + entity reference is parsed, ExternalEntityRefHandler is called with + the public and system ids from the document. + """ + handler_call_args = [] + def resolve_entity(context, base, system_id, public_id): + handler_call_args.append((public_id, system_id)) + return 1 + + parser = expat.ParserCreate() + parser.UseForeignDTD(True) + parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_ALWAYS) + parser.ExternalEntityRefHandler = resolve_entity + parser.Parse( + "") + self.assertEqual(handler_call_args, [("bar", "baz")]) + + def test_main(): run_unittest(SetAttributeTest, ParseTest, @@ -598,7 +650,8 @@ PositionTest, sf1296433Test, ChardataBufferTest, - MalformedInputText) + MalformedInputText, + ForeignDTDTests) if __name__ == "__main__": test_main() diff --git a/lib-python/2.7/test/test_random.py b/lib-python/2.7/test/test_random.py --- a/lib-python/2.7/test/test_random.py +++ b/lib-python/2.7/test/test_random.py @@ -57,6 +57,14 @@ self.assertRaises(TypeError, self.gen.jumpahead) # needs an arg self.assertRaises(TypeError, self.gen.jumpahead, 2, 3) # too many + def test_jumpahead_produces_valid_state(self): + # From http://bugs.python.org/issue14591. + self.gen.seed(199210368) + self.gen.jumpahead(13550674232554645900) + for i in range(500): + val = self.gen.random() + self.assertLess(val, 1.0) + def test_sample(self): # For the entire allowable range of 0 <= k <= N, validate that # the sample is of the correct length and contains only unique items @@ -486,6 +494,7 @@ g.random = x[:].pop; g.paretovariate(1.0) g.random = x[:].pop; g.expovariate(1.0) g.random = x[:].pop; g.weibullvariate(1.0, 1.0) + g.random = x[:].pop; g.vonmisesvariate(1.0, 1.0) g.random = x[:].pop; g.normalvariate(0.0, 1.0) g.random = x[:].pop; g.gauss(0.0, 1.0) g.random = x[:].pop; g.lognormvariate(0.0, 1.0) @@ -506,6 +515,7 @@ (g.uniform, (1.0,10.0), (10.0+1.0)/2, (10.0-1.0)**2/12), (g.triangular, (0.0, 1.0, 1.0/3.0), 4.0/9.0, 7.0/9.0/18.0), (g.expovariate, (1.5,), 1/1.5, 1/1.5**2), + (g.vonmisesvariate, (1.23, 0), pi, pi**2/3), (g.paretovariate, (5.0,), 5.0/(5.0-1), 5.0/((5.0-1)**2*(5.0-2))), (g.weibullvariate, (1.0, 3.0), gamma(1+1/3.0), @@ -522,8 +532,50 @@ s1 += e s2 += (e - mu) ** 2 N = len(y) - self.assertAlmostEqual(s1/N, mu, 2) - self.assertAlmostEqual(s2/(N-1), sigmasqrd, 2) + self.assertAlmostEqual(s1/N, mu, places=2, + msg='%s%r' % (variate.__name__, args)) + self.assertAlmostEqual(s2/(N-1), sigmasqrd, places=2, + msg='%s%r' % (variate.__name__, args)) + + def test_constant(self): + g = random.Random() + N = 100 + for variate, args, expected in [ + (g.uniform, (10.0, 10.0), 10.0), + (g.triangular, (10.0, 10.0), 10.0), + #(g.triangular, (10.0, 10.0, 10.0), 10.0), + (g.expovariate, (float('inf'),), 0.0), + (g.vonmisesvariate, (3.0, float('inf')), 3.0), + (g.gauss, (10.0, 0.0), 10.0), + (g.lognormvariate, (0.0, 0.0), 1.0), + (g.lognormvariate, (-float('inf'), 0.0), 0.0), + (g.normalvariate, (10.0, 0.0), 10.0), + (g.paretovariate, (float('inf'),), 1.0), + (g.weibullvariate, (10.0, float('inf')), 10.0), + (g.weibullvariate, (0.0, 10.0), 0.0), + ]: + for i in range(N): + self.assertEqual(variate(*args), expected) + + def test_von_mises_range(self): + # Issue 17149: von mises variates were not consistently in the + # range [0, 2*PI]. + g = random.Random() + N = 100 + for mu in 0.0, 0.1, 3.1, 6.2: + for kappa in 0.0, 2.3, 500.0: + for _ in range(N): + sample = g.vonmisesvariate(mu, kappa) + self.assertTrue( + 0 <= sample <= random.TWOPI, + msg=("vonmisesvariate({}, {}) produced a result {} out" + " of range [0, 2*pi]").format(mu, kappa, sample)) + + def test_von_mises_large_kappa(self): + # Issue #17141: vonmisesvariate() was hang for large kappas + random.vonmisesvariate(0, 1e15) + random.vonmisesvariate(0, 1e100) + class TestModule(unittest.TestCase): def testMagicConstants(self): diff --git a/lib-python/2.7/test/test_re.py b/lib-python/2.7/test/test_re.py --- a/lib-python/2.7/test/test_re.py +++ b/lib-python/2.7/test/test_re.py @@ -1,4 +1,5 @@ from test.test_support import verbose, run_unittest, import_module +from test.test_support import precisionbigmemtest, _2G, cpython_only import re from re import Scanner import sys @@ -6,6 +7,7 @@ import traceback from weakref import proxy + # Misc tests from Tim Peters' re.doc # WARNING: Don't change details in these tests if you don't know @@ -174,11 +176,31 @@ self.assertEqual(re.sub('x*', '-', 'abxd'), '-a-b-d-') self.assertEqual(re.sub('x+', '-', 'abxd'), 'ab-d') + def test_symbolic_groups(self): + re.compile('(?Px)(?P=a)(?(a)y)') + re.compile('(?Px)(?P=a1)(?(a1)y)') + self.assertRaises(re.error, re.compile, '(?P)(?P)') + self.assertRaises(re.error, re.compile, '(?Px)') + self.assertRaises(re.error, re.compile, '(?P=)') + self.assertRaises(re.error, re.compile, '(?P=1)') + self.assertRaises(re.error, re.compile, '(?P=a)') + self.assertRaises(re.error, re.compile, '(?P=a1)') + self.assertRaises(re.error, re.compile, '(?P=a.)') + self.assertRaises(re.error, re.compile, '(?P<)') + self.assertRaises(re.error, re.compile, '(?P<>)') + self.assertRaises(re.error, re.compile, '(?P<1>)') + self.assertRaises(re.error, re.compile, '(?P)') + self.assertRaises(re.error, re.compile, '(?())') + self.assertRaises(re.error, re.compile, '(?(a))') + self.assertRaises(re.error, re.compile, '(?(1a))') + self.assertRaises(re.error, re.compile, '(?(a.))') + def test_symbolic_refs(self): self.assertRaises(re.error, re.sub, '(?Px)', '\gx)', '\g<', 'xx') self.assertRaises(re.error, re.sub, '(?Px)', '\g', 'xx') self.assertRaises(re.error, re.sub, '(?Px)', '\g', 'xx') + self.assertRaises(re.error, re.sub, '(?Px)', '\g<>', 'xx') self.assertRaises(re.error, re.sub, '(?Px)', '\g<1a1>', 'xx') self.assertRaises(IndexError, re.sub, '(?Px)', '\g', 'xx') self.assertRaises(re.error, re.sub, '(?Px)|(?Py)', '\g', 'xx') @@ -405,6 +427,12 @@ self.assertEqual(re.match(u"([\u2222\u2223])", u"\u2222", re.UNICODE).group(1), u"\u2222") + def test_big_codesize(self): + # Issue #1160 + r = re.compile('|'.join(('%d'%x for x in range(10000)))) + self.assertIsNotNone(r.match('1000')) + self.assertIsNotNone(r.match('9999')) + def test_anyall(self): self.assertEqual(re.match("a.b", "a\nb", re.DOTALL).group(0), "a\nb") @@ -600,6 +628,15 @@ self.assertEqual(re.match('(x)*y', 50000*'x'+'y').group(1), 'x') self.assertEqual(re.match('(x)*?y', 50000*'x'+'y').group(1), 'x') + def test_unlimited_zero_width_repeat(self): + # Issue #9669 + self.assertIsNone(re.match(r'(?:a?)*y', 'z')) + self.assertIsNone(re.match(r'(?:a?)+y', 'z')) + self.assertIsNone(re.match(r'(?:a?){2,}y', 'z')) + self.assertIsNone(re.match(r'(?:a?)*?y', 'z')) + self.assertIsNone(re.match(r'(?:a?)+?y', 'z')) + self.assertIsNone(re.match(r'(?:a?){2,}?y', 'z')) + def test_scanner(self): def s_ident(scanner, token): return token def s_operator(scanner, token): return "op%s" % token @@ -793,6 +830,63 @@ # Test behaviour when not given a string or pattern as parameter self.assertRaises(TypeError, re.compile, 0) + def test_bug_13899(self): + # Issue #13899: re pattern r"[\A]" should work like "A" but matches + # nothing. Ditto B and Z. + self.assertEqual(re.findall(r'[\A\B\b\C\Z]', 'AB\bCZ'), + ['A', 'B', '\b', 'C', 'Z']) + + @precisionbigmemtest(size=_2G, memuse=1) + def test_large_search(self, size): + # Issue #10182: indices were 32-bit-truncated. + s = 'a' * size + m = re.search('$', s) + self.assertIsNotNone(m) + self.assertEqual(m.start(), size) + self.assertEqual(m.end(), size) + + # The huge memuse is because of re.sub() using a list and a join() + # to create the replacement result. + @precisionbigmemtest(size=_2G, memuse=16 + 2) + def test_large_subn(self, size): + # Issue #10182: indices were 32-bit-truncated. + s = 'a' * size + r, n = re.subn('', '', s) + self.assertEqual(r, s) + self.assertEqual(n, size + 1) + + + def test_repeat_minmax_overflow(self): + # Issue #13169 + string = "x" * 100000 + self.assertEqual(re.match(r".{65535}", string).span(), (0, 65535)) + self.assertEqual(re.match(r".{,65535}", string).span(), (0, 65535)) + self.assertEqual(re.match(r".{65535,}?", string).span(), (0, 65535)) + self.assertEqual(re.match(r".{65536}", string).span(), (0, 65536)) + self.assertEqual(re.match(r".{,65536}", string).span(), (0, 65536)) + self.assertEqual(re.match(r".{65536,}?", string).span(), (0, 65536)) + # 2**128 should be big enough to overflow both SRE_CODE and Py_ssize_t. + self.assertRaises(OverflowError, re.compile, r".{%d}" % 2**128) + self.assertRaises(OverflowError, re.compile, r".{,%d}" % 2**128) + self.assertRaises(OverflowError, re.compile, r".{%d,}?" % 2**128) + self.assertRaises(OverflowError, re.compile, r".{%d,%d}" % (2**129, 2**128)) + + @cpython_only + def test_repeat_minmax_overflow_maxrepeat(self): + try: + from _sre import MAXREPEAT + except ImportError: + self.skipTest('requires _sre.MAXREPEAT constant') + string = "x" * 100000 + self.assertIsNone(re.match(r".{%d}" % (MAXREPEAT - 1), string)) + self.assertEqual(re.match(r".{,%d}" % (MAXREPEAT - 1), string).span(), + (0, 100000)) + self.assertIsNone(re.match(r".{%d,}?" % (MAXREPEAT - 1), string)) + self.assertRaises(OverflowError, re.compile, r".{%d}" % MAXREPEAT) + self.assertRaises(OverflowError, re.compile, r".{,%d}" % MAXREPEAT) + self.assertRaises(OverflowError, re.compile, r".{%d,}?" % MAXREPEAT) + + def run_re_tests(): from test.re_tests import tests, SUCCEED, FAIL, SYNTAX_ERROR if verbose: diff --git a/lib-python/2.7/test/test_readline.py b/lib-python/2.7/test/test_readline.py --- a/lib-python/2.7/test/test_readline.py +++ b/lib-python/2.7/test/test_readline.py @@ -12,6 +12,10 @@ readline = import_module('readline') class TestHistoryManipulation (unittest.TestCase): + + @unittest.skipIf(not hasattr(readline, 'clear_history'), + "The history update test cannot be run because the " + "clear_history method is not available.") def testHistoryUpdates(self): readline.clear_history() diff --git a/lib-python/2.7/test/test_resource.py b/lib-python/2.7/test/test_resource.py --- a/lib-python/2.7/test/test_resource.py +++ b/lib-python/2.7/test/test_resource.py @@ -103,6 +103,23 @@ except (ValueError, AttributeError): pass + # Issue 6083: Reference counting bug + def test_setrusage_refcount(self): + try: + limits = resource.getrlimit(resource.RLIMIT_CPU) + except AttributeError: + pass + else: + class BadSequence: + def __len__(self): + return 2 + def __getitem__(self, key): + if key in (0, 1): + return len(tuple(range(1000000))) + raise IndexError + + resource.setrlimit(resource.RLIMIT_CPU, BadSequence()) + def test_main(verbose=None): test_support.run_unittest(ResourceTest) diff --git a/lib-python/2.7/test/test_sax.py b/lib-python/2.7/test/test_sax.py --- a/lib-python/2.7/test/test_sax.py +++ b/lib-python/2.7/test/test_sax.py @@ -14,12 +14,28 @@ from xml.sax.handler import feature_namespaces from xml.sax.xmlreader import InputSource, AttributesImpl, AttributesNSImpl from cStringIO import StringIO +import io +import os.path +import shutil +import test.test_support as support from test.test_support import findfile, run_unittest import unittest TEST_XMLFILE = findfile("test.xml", subdir="xmltestdata") TEST_XMLFILE_OUT = findfile("test.xml.out", subdir="xmltestdata") +supports_unicode_filenames = True +if not os.path.supports_unicode_filenames: + try: + support.TESTFN_UNICODE.encode(support.TESTFN_ENCODING) + except (AttributeError, UnicodeError, TypeError): + # Either the file system encoding is None, or the file name + # cannot be encoded in the file system encoding. + supports_unicode_filenames = False +requires_unicode_filenames = unittest.skipUnless( + supports_unicode_filenames, + 'Requires unicode filenames support') + ns_uri = "http://www.python.org/xml-ns/saxtest/" class XmlTestBase(unittest.TestCase): @@ -155,9 +171,9 @@ start = '\n' -class XmlgenTest(unittest.TestCase): +class XmlgenTest: def test_xmlgen_basic(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() gen.startElement("doc", {}) @@ -167,7 +183,7 @@ self.assertEqual(result.getvalue(), start + "") def test_xmlgen_content(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -179,7 +195,7 @@ self.assertEqual(result.getvalue(), start + "huhei") def test_xmlgen_pi(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -191,7 +207,7 @@ self.assertEqual(result.getvalue(), start + "") def test_xmlgen_content_escape(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -204,7 +220,7 @@ start + "<huhei&") def test_xmlgen_attr_escape(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -223,8 +239,41 @@ "" "")) + def test_xmlgen_encoding(self): + encodings = ('iso-8859-15', 'utf-8', + 'utf-16be', 'utf-16le', + 'utf-32be', 'utf-32le') + for encoding in encodings: + result = self.ioclass() + gen = XMLGenerator(result, encoding=encoding) + + gen.startDocument() + gen.startElement("doc", {"a": u'\u20ac'}) + gen.characters(u"\u20ac") + gen.endElement("doc") + gen.endDocument() + + self.assertEqual(result.getvalue(), ( + u'\n' + u'\u20ac' % encoding + ).encode(encoding, 'xmlcharrefreplace')) + + def test_xmlgen_unencodable(self): + result = self.ioclass() + gen = XMLGenerator(result, encoding='ascii') + + gen.startDocument() + gen.startElement("doc", {"a": u'\u20ac'}) + gen.characters(u"\u20ac") + gen.endElement("doc") + gen.endDocument() + + self.assertEqual(result.getvalue(), + '\n' + '') + def test_xmlgen_ignorable(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -236,7 +285,7 @@ self.assertEqual(result.getvalue(), start + " ") def test_xmlgen_ns(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -254,7 +303,7 @@ ns_uri)) def test_1463026_1(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -265,7 +314,7 @@ self.assertEqual(result.getvalue(), start+'') def test_1463026_2(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -278,7 +327,7 @@ self.assertEqual(result.getvalue(), start+'') def test_1463026_3(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -294,7 +343,7 @@ def test_5027_1(self): # The xml prefix (as in xml:lang below) is reserved and bound by # definition to http://www.w3.org/XML/1998/namespace. XMLGenerator had - # a bug whereby a KeyError is thrown because this namespace is missing + # a bug whereby a KeyError is raised because this namespace is missing # from a dictionary. # # This test demonstrates the bug by parsing a document. @@ -306,7 +355,7 @@ parser = make_parser() parser.setFeature(feature_namespaces, True) - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) parser.setContentHandler(gen) parser.parse(test_xml) @@ -320,12 +369,12 @@ def test_5027_2(self): # The xml prefix (as in xml:lang below) is reserved and bound by # definition to http://www.w3.org/XML/1998/namespace. XMLGenerator had - # a bug whereby a KeyError is thrown because this namespace is missing + # a bug whereby a KeyError is raised because this namespace is missing # from a dictionary. # # This test demonstrates the bug by direct manipulation of the # XMLGenerator. - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -345,6 +394,44 @@ 'Hello' '')) + def test_no_close_file(self): + result = self.ioclass() + def func(out): + gen = XMLGenerator(out) + gen.startDocument() + gen.startElement("doc", {}) + func(result) + self.assertFalse(result.closed) + + def test_xmlgen_fragment(self): + result = self.ioclass() + gen = XMLGenerator(result) + + # Don't call gen.startDocument() + gen.startElement("foo", {"a": "1.0"}) + gen.characters("Hello") + gen.endElement("foo") + gen.startElement("bar", {"b": "2.0"}) + gen.endElement("bar") + # Don't call gen.endDocument() + + self.assertEqual(result.getvalue(), + 'Hello') + +class StringXmlgenTest(XmlgenTest, unittest.TestCase): + ioclass = StringIO + +class BytesIOXmlgenTest(XmlgenTest, unittest.TestCase): + ioclass = io.BytesIO + +class WriterXmlgenTest(XmlgenTest, unittest.TestCase): + class ioclass(list): + write = list.append + closed = False + + def getvalue(self): + return b''.join(self) + class XMLFilterBaseTest(unittest.TestCase): def test_filter_basic(self): @@ -384,6 +471,21 @@ self.assertEqual(result.getvalue(), xml_test_out) + @requires_unicode_filenames + def test_expat_file_unicode(self): + fname = support.TESTFN_UNICODE + shutil.copyfile(TEST_XMLFILE, fname) + self.addCleanup(support.unlink, fname) + + parser = create_parser() + result = StringIO() + xmlgen = XMLGenerator(result) + + parser.setContentHandler(xmlgen) + parser.parse(open(fname)) + + self.assertEqual(result.getvalue(), xml_test_out) + # ===== DTDHandler support class TestDTDHandler: @@ -523,6 +625,21 @@ self.assertEqual(result.getvalue(), xml_test_out) + @requires_unicode_filenames + def test_expat_inpsource_sysid_unicode(self): + fname = support.TESTFN_UNICODE + shutil.copyfile(TEST_XMLFILE, fname) + self.addCleanup(support.unlink, fname) + + parser = create_parser() + result = StringIO() + xmlgen = XMLGenerator(result) + + parser.setContentHandler(xmlgen) + parser.parse(InputSource(fname)) + + self.assertEqual(result.getvalue(), xml_test_out) + def test_expat_inpsource_stream(self): parser = create_parser() result = StringIO() @@ -596,6 +713,21 @@ self.assertEqual(parser.getSystemId(), TEST_XMLFILE) self.assertEqual(parser.getPublicId(), None) + @requires_unicode_filenames + def test_expat_locator_withinfo_unicode(self): + fname = support.TESTFN_UNICODE + shutil.copyfile(TEST_XMLFILE, fname) + self.addCleanup(support.unlink, fname) + + result = StringIO() + xmlgen = XMLGenerator(result) + parser = create_parser() + parser.setContentHandler(xmlgen) + parser.parse(fname) + + self.assertEqual(parser.getSystemId(), fname) + self.assertEqual(parser.getPublicId(), None) + # =========================================================================== # @@ -744,7 +876,9 @@ def test_main(): run_unittest(MakeParserTest, SaxutilsTest, - XmlgenTest, + StringXmlgenTest, + BytesIOXmlgenTest, + WriterXmlgenTest, ExpatReaderTest, ErrorReportingTest, XmlReaderTest) diff --git a/lib-python/2.7/test/test_select.py b/lib-python/2.7/test/test_select.py --- a/lib-python/2.7/test/test_select.py +++ b/lib-python/2.7/test/test_select.py @@ -49,6 +49,15 @@ self.fail('Unexpected return values from select():', rfd, wfd, xfd) p.close() + # Issue 16230: Crash on select resized list + def test_select_mutated(self): + a = [] + class F: + def fileno(self): + del a[-1] + return sys.__stdout__.fileno() + a[:] = [F()] * 10 + self.assertEqual(select.select([], a, []), ([], a[:5], [])) def test_main(): test_support.run_unittest(SelectTestCase) diff --git a/lib-python/2.7/test/test_shutil.py b/lib-python/2.7/test/test_shutil.py --- a/lib-python/2.7/test/test_shutil.py +++ b/lib-python/2.7/test/test_shutil.py @@ -7,6 +7,7 @@ import stat import os import os.path +import errno from os.path import splitdrive from distutils.spawn import find_executable, spawn from shutil import (_make_tarball, _make_zipfile, make_archive, @@ -339,6 +340,35 @@ shutil.rmtree(TESTFN, ignore_errors=True) shutil.rmtree(TESTFN2, ignore_errors=True) + @unittest.skipUnless(hasattr(os, 'chflags') and + hasattr(errno, 'EOPNOTSUPP') and + hasattr(errno, 'ENOTSUP'), + "requires os.chflags, EOPNOTSUPP & ENOTSUP") + def test_copystat_handles_harmless_chflags_errors(self): + tmpdir = self.mkdtemp() + file1 = os.path.join(tmpdir, 'file1') + file2 = os.path.join(tmpdir, 'file2') + self.write_file(file1, 'xxx') + self.write_file(file2, 'xxx') + + def make_chflags_raiser(err): + ex = OSError() + + def _chflags_raiser(path, flags): + ex.errno = err + raise ex + return _chflags_raiser + old_chflags = os.chflags + try: + for err in errno.EOPNOTSUPP, errno.ENOTSUP: + os.chflags = make_chflags_raiser(err) + shutil.copystat(file1, file2) + # assert others errors break it + os.chflags = make_chflags_raiser(errno.EOPNOTSUPP + errno.ENOTSUP) + self.assertRaises(OSError, shutil.copystat, file1, file2) + finally: + os.chflags = old_chflags + @unittest.skipUnless(zlib, "requires zlib") def test_make_tarball(self): # creating something to tar diff --git a/lib-python/2.7/test/test_signal.py b/lib-python/2.7/test/test_signal.py --- a/lib-python/2.7/test/test_signal.py +++ b/lib-python/2.7/test/test_signal.py @@ -109,7 +109,7 @@ # This wait should be interrupted by the signal's exception. self.wait(child) time.sleep(1) # Give the signal time to be delivered. - self.fail('HandlerBCalled exception not thrown') + self.fail('HandlerBCalled exception not raised') except HandlerBCalled: self.assertTrue(self.b_called) self.assertFalse(self.a_called) @@ -148,7 +148,7 @@ # test-running process from all the signals. It then # communicates with that child process over a pipe and # re-raises information about any exceptions the child - # throws. The real work happens in self.run_test(). + # raises. The real work happens in self.run_test(). os_done_r, os_done_w = os.pipe() with closing(os.fdopen(os_done_r)) as done_r, \ closing(os.fdopen(os_done_w, 'w')) as done_w: @@ -227,6 +227,13 @@ signal.signal(7, handler) +class WakeupFDTests(unittest.TestCase): + + def test_invalid_fd(self): + fd = test_support.make_bad_fd() + self.assertRaises(ValueError, signal.set_wakeup_fd, fd) + + @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") class WakeupSignalTests(unittest.TestCase): TIMEOUT_FULL = 10 @@ -485,8 +492,9 @@ def test_main(): test_support.run_unittest(BasicSignalTests, InterProcessSignalTests, - WakeupSignalTests, SiginterruptTest, - ItimerTest, WindowsSignalTests) + WakeupFDTests, WakeupSignalTests, + SiginterruptTest, ItimerTest, + WindowsSignalTests) if __name__ == "__main__": diff --git a/lib-python/2.7/test/test_socket.py b/lib-python/2.7/test/test_socket.py --- a/lib-python/2.7/test/test_socket.py +++ b/lib-python/2.7/test/test_socket.py @@ -6,6 +6,7 @@ import errno import socket import select +import _testcapi import time import traceback import Queue @@ -644,9 +645,10 @@ if SUPPORTS_IPV6: socket.getaddrinfo('::1', 80) # port can be a string service name such as "http", a numeric - # port number or None + # port number (int or long), or None socket.getaddrinfo(HOST, "http") socket.getaddrinfo(HOST, 80) + socket.getaddrinfo(HOST, 80L) socket.getaddrinfo(HOST, None) # test family and socktype filters infos = socket.getaddrinfo(HOST, None, socket.AF_INET) @@ -699,11 +701,17 @@ def test_sendall_interrupted_with_timeout(self): self.check_sendall_interrupted(True) - def testListenBacklog0(self): + def test_listen_backlog(self): + for backlog in 0, -1: + srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + srv.bind((HOST, 0)) + srv.listen(backlog) + srv.close() + + # Issue 15989 srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.bind((HOST, 0)) - # backlog = 0 - srv.listen(0) + self.assertRaises(OverflowError, srv.listen, _testcapi.INT_MAX + 1) srv.close() @unittest.skipUnless(SUPPORTS_IPV6, 'IPv6 required for this test.') @@ -807,6 +815,11 @@ def _testShutdown(self): self.serv_conn.send(MSG) + # Issue 15989 + self.assertRaises(OverflowError, self.serv_conn.shutdown, + _testcapi.INT_MAX + 1) + self.assertRaises(OverflowError, self.serv_conn.shutdown, + 2 + (_testcapi.UINT_MAX + 1)) self.serv_conn.shutdown(2) @unittest.skipUnless(thread, 'Threading required for this test.') @@ -882,7 +895,10 @@ def testSetBlocking(self): # Testing whether set blocking works - self.serv.setblocking(0) + self.serv.setblocking(True) + self.assertIsNone(self.serv.gettimeout()) + self.serv.setblocking(False) + self.assertEqual(self.serv.gettimeout(), 0.0) start = time.time() try: self.serv.accept() @@ -890,6 +906,10 @@ pass end = time.time() self.assertTrue((end - start) < 1.0, "Error setting non-blocking mode.") + # Issue 15989 + if _testcapi.UINT_MAX < _testcapi.ULONG_MAX: + self.serv.setblocking(_testcapi.UINT_MAX + 1) + self.assertIsNone(self.serv.gettimeout()) def _testSetBlocking(self): pass @@ -961,8 +981,8 @@ def tearDown(self): self.serv_file.close() self.assertTrue(self.serv_file.closed) + SocketConnectedTest.tearDown(self) self.serv_file = None - SocketConnectedTest.tearDown(self) def clientSetUp(self): SocketConnectedTest.clientSetUp(self) @@ -1150,6 +1170,64 @@ bufsize = 1 # Default-buffered for reading; line-buffered for writing + class SocketMemo(object): + """A wrapper to keep track of sent data, needed to examine write behaviour""" + def __init__(self, sock): + self._sock = sock + self.sent = [] + + def send(self, data, flags=0): + n = self._sock.send(data, flags) + self.sent.append(data[:n]) + return n + + def sendall(self, data, flags=0): + self._sock.sendall(data, flags) + self.sent.append(data) + + def __getattr__(self, attr): + return getattr(self._sock, attr) + + def getsent(self): + return [e.tobytes() if isinstance(e, memoryview) else e for e in self.sent] + + def setUp(self): + FileObjectClassTestCase.setUp(self) + self.serv_file._sock = self.SocketMemo(self.serv_file._sock) + + def testLinebufferedWrite(self): + # Write two lines, in small chunks + msg = MSG.strip() + print >> self.serv_file, msg, + print >> self.serv_file, msg + + # second line: + print >> self.serv_file, msg, + print >> self.serv_file, msg, + print >> self.serv_file, msg + + # third line + print >> self.serv_file, '' + + self.serv_file.flush() + + msg1 = "%s %s\n"%(msg, msg) + msg2 = "%s %s %s\n"%(msg, msg, msg) + msg3 = "\n" + self.assertEqual(self.serv_file._sock.getsent(), [msg1, msg2, msg3]) + + def _testLinebufferedWrite(self): + msg = MSG.strip() + msg1 = "%s %s\n"%(msg, msg) + msg2 = "%s %s %s\n"%(msg, msg, msg) + msg3 = "\n" + l1 = self.cli_file.readline() + self.assertEqual(l1, msg1) + l2 = self.cli_file.readline() + self.assertEqual(l2, msg2) + l3 = self.cli_file.readline() + self.assertEqual(l3, msg3) + class SmallBufferedFileObjectClassTestCase(FileObjectClassTestCase): @@ -1197,7 +1275,26 @@ port = test_support.find_unused_port() with self.assertRaises(socket.error) as cm: socket.create_connection((HOST, port)) - self.assertEqual(cm.exception.errno, errno.ECONNREFUSED) + + # Issue #16257: create_connection() calls getaddrinfo() against + # 'localhost'. This may result in an IPV6 addr being returned + # as well as an IPV4 one: + # >>> socket.getaddrinfo('localhost', port, 0, SOCK_STREAM) + # >>> [(2, 2, 0, '', ('127.0.0.1', 41230)), + # (26, 2, 0, '', ('::1', 41230, 0, 0))] + # + # create_connection() enumerates through all the addresses returned + # and if it doesn't successfully bind to any of them, it propagates + # the last exception it encountered. + # + # On Solaris, ENETUNREACH is returned in this circumstance instead + # of ECONNREFUSED. So, if that errno exists, add it to our list of + # expected errnos. + expected_errnos = [ errno.ECONNREFUSED, ] + if hasattr(errno, 'ENETUNREACH'): + expected_errnos.append(errno.ENETUNREACH) + + self.assertIn(cm.exception.errno, expected_errnos) def test_create_connection_timeout(self): # Issue #9792: create_connection() should not recast timeout errors diff --git a/lib-python/2.7/test/test_socketserver.py b/lib-python/2.7/test/test_socketserver.py --- a/lib-python/2.7/test/test_socketserver.py +++ b/lib-python/2.7/test/test_socketserver.py @@ -8,6 +8,8 @@ import select import signal import socket +import select +import errno import tempfile import unittest import SocketServer @@ -32,8 +34,11 @@ if hasattr(signal, 'alarm'): signal.alarm(n) +# Remember real select() to avoid interferences with mocking +_real_select = select.select + def receive(sock, n, timeout=20): - r, w, x = select.select([sock], [], [], timeout) + r, w, x = _real_select([sock], [], [], timeout) if sock in r: return sock.recv(n) else: @@ -53,7 +58,7 @@ def simple_subprocess(testcase): pid = os.fork() if pid == 0: - # Don't throw an exception; it would be caught by the test harness. + # Don't raise an exception; it would be caught by the test harness. os._exit(72) yield None pid2, status = os.waitpid(pid, 0) @@ -225,6 +230,38 @@ SocketServer.DatagramRequestHandler, self.dgram_examine) + @contextlib.contextmanager + def mocked_select_module(self): + """Mocks the select.select() call to raise EINTR for first call""" + old_select = select.select + + class MockSelect: + def __init__(self): + self.called = 0 + + def __call__(self, *args): + self.called += 1 + if self.called == 1: + # raise the exception on first call + raise select.error(errno.EINTR, os.strerror(errno.EINTR)) + else: + # Return real select value for consecutive calls + return old_select(*args) + + select.select = MockSelect() + try: + yield select.select + finally: + select.select = old_select + + def test_InterruptServerSelectCall(self): + with self.mocked_select_module() as mock_select: + pid = self.run_server(SocketServer.TCPServer, + SocketServer.StreamRequestHandler, + self.stream_examine) + # Make sure select was called again: + self.assertGreater(mock_select.called, 1) + # Alas, on Linux (at least) recvfrom() doesn't return a meaningful # client address so this cannot work: diff --git a/lib-python/2.7/test/test_ssl.py b/lib-python/2.7/test/test_ssl.py --- a/lib-python/2.7/test/test_ssl.py +++ b/lib-python/2.7/test/test_ssl.py @@ -95,12 +95,8 @@ sys.stdout.write("\n RAND_status is %d (%s)\n" % (v, (v and "sufficient randomness") or "insufficient randomness")) - try: - ssl.RAND_egd(1) - except TypeError: - pass - else: - print "didn't raise TypeError" + self.assertRaises(TypeError, ssl.RAND_egd, 1) + self.assertRaises(TypeError, ssl.RAND_egd, 'foo', 1) ssl.RAND_add("this is a random string", 75.0) def test_parse_cert(self): @@ -111,13 +107,12 @@ if test_support.verbose: sys.stdout.write("\n" + pprint.pformat(p) + "\n") self.assertEqual(p['subject'], - ((('countryName', u'US'),), - (('stateOrProvinceName', u'Delaware'),), - (('localityName', u'Wilmington'),), - (('organizationName', u'Python Software Foundation'),), - (('organizationalUnitName', u'SSL'),), - (('commonName', u'somemachine.python.org'),)), + ((('countryName', 'XY'),), + (('localityName', 'Castle Anthrax'),), + (('organizationName', 'Python Software Foundation'),), + (('commonName', 'localhost'),)) ) + self.assertEqual(p['subjectAltName'], (('DNS', 'localhost'),)) # Issue #13034: the subjectAltName in some certificates # (notably projects.developer.nokia.com:443) wasn't parsed p = ssl._ssl._test_decode_cert(NOKIACERT) @@ -284,6 +279,34 @@ finally: s.close() + def test_timeout_connect_ex(self): + # Issue #12065: on a timeout, connect_ex() should return the original + # errno (mimicking the behaviour of non-SSL sockets). + with test_support.transient_internet("svn.python.org"): + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=SVN_PYTHON_ORG_ROOT_CERT, + do_handshake_on_connect=False) + try: + s.settimeout(0.0000001) + rc = s.connect_ex(('svn.python.org', 443)) + if rc == 0: + self.skipTest("svn.python.org responded too quickly") + self.assertIn(rc, (errno.EAGAIN, errno.EWOULDBLOCK)) + finally: + s.close() + + def test_connect_ex_error(self): + with test_support.transient_internet("svn.python.org"): + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=SVN_PYTHON_ORG_ROOT_CERT) + try: + self.assertEqual(errno.ECONNREFUSED, + s.connect_ex(("svn.python.org", 444))) + finally: + s.close() + @unittest.skipIf(os.name == "nt", "Can't use a socket as a file under Windows") def test_makefile_close(self): # Issue #5238: creating a file-like object with makefile() shouldn't @@ -355,7 +378,8 @@ # SHA256 was added in OpenSSL 0.9.8 if ssl.OPENSSL_VERSION_INFO < (0, 9, 8, 0, 15): self.skipTest("SHA256 not available on %r" % ssl.OPENSSL_VERSION) - # NOTE: https://sha256.tbs-internet.com is another possible test host + self.skipTest("remote host needs SNI, only available on Python 3.2+") + # NOTE: https://sha2.hboeck.de is another possible test host remote = ("sha256.tbs-internet.com", 443) sha256_cert = os.path.join(os.path.dirname(__file__), "sha256.pem") with test_support.transient_internet("sha256.tbs-internet.com"): diff --git a/lib-python/2.7/test/test_str.py b/lib-python/2.7/test/test_str.py --- a/lib-python/2.7/test/test_str.py +++ b/lib-python/2.7/test/test_str.py @@ -35,6 +35,18 @@ string_tests.MixinStrUnicodeUserStringTest.test_formatting(self) self.assertRaises(OverflowError, '%c'.__mod__, 0x1234) + @test_support.cpython_only + def test_formatting_huge_precision(self): + from _testcapi import INT_MAX + format_string = "%.{}f".format(INT_MAX + 1) + with self.assertRaises(ValueError): + result = format_string % 2.34 + + def test_formatting_huge_width(self): + format_string = "%{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format_string % 2.34 + def test_conversion(self): # Make sure __str__() behaves properly class Foo0: @@ -371,6 +383,21 @@ self.assertRaises(ValueError, format, "", "-") self.assertRaises(ValueError, "{0:=s}".format, '') + def test_format_huge_precision(self): + format_string = ".{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format(2.34, format_string) + + def test_format_huge_width(self): + format_string = "{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format(2.34, format_string) + + def test_format_huge_item_number(self): + format_string = "{{{}:.6f}}".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format_string.format(2.34) + def test_format_auto_numbering(self): class C: def __init__(self, x=100): diff --git a/lib-python/2.7/test/test_strptime.py b/lib-python/2.7/test/test_strptime.py --- a/lib-python/2.7/test/test_strptime.py +++ b/lib-python/2.7/test/test_strptime.py @@ -378,6 +378,14 @@ need_escaping = ".^$*+?{}\[]|)(" self.assertTrue(_strptime._strptime_time(need_escaping, need_escaping)) + def test_feb29_on_leap_year_without_year(self): + time.strptime("Feb 29", "%b %d") + + def test_mar1_comes_after_feb29_even_when_omitting_the_year(self): + self.assertLess( + time.strptime("Feb 29", "%b %d"), + time.strptime("Mar 1", "%b %d")) + class Strptime12AMPMTests(unittest.TestCase): """Test a _strptime regression in '%I %p' at 12 noon (12 PM)""" diff --git a/lib-python/2.7/test/test_struct.py b/lib-python/2.7/test/test_struct.py --- a/lib-python/2.7/test/test_struct.py +++ b/lib-python/2.7/test/test_struct.py @@ -3,7 +3,8 @@ import unittest import struct import inspect -from test.test_support import run_unittest, check_warnings, check_py3k_warnings +from test import test_support as support +from test.test_support import (check_warnings, check_py3k_warnings) import sys ISBIGENDIAN = sys.byteorder == "big" @@ -544,8 +545,29 @@ hugecount2 = '{}b{}H'.format(sys.maxsize//2, sys.maxsize//2) self.assertRaises(struct.error, struct.calcsize, hugecount2) + def check_sizeof(self, format_str, number_of_codes): + # The size of 'PyStructObject' + totalsize = support.calcobjsize('5P') + # The size taken up by the 'formatcode' dynamic array + totalsize += struct.calcsize('3P') * (number_of_codes + 1) + support.check_sizeof(self, struct.Struct(format_str), totalsize) + + @support.cpython_only + def test__sizeof__(self): + for code in integer_codes: + self.check_sizeof(code, 1) + self.check_sizeof('BHILfdspP', 9) + self.check_sizeof('B' * 1234, 1234) + self.check_sizeof('fd', 2) + self.check_sizeof('xxxxxxxxxxxxxx', 0) + self.check_sizeof('100H', 100) + self.check_sizeof('187s', 1) + self.check_sizeof('20p', 1) + self.check_sizeof('0s', 1) + self.check_sizeof('0c', 0) + def test_main(): - run_unittest(StructTest) + support.run_unittest(StructTest) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_subprocess.py b/lib-python/2.7/test/test_subprocess.py --- a/lib-python/2.7/test/test_subprocess.py +++ b/lib-python/2.7/test/test_subprocess.py @@ -58,6 +58,18 @@ self.assertEqual(actual, expected, msg) +class PopenTestException(Exception): + pass + + +class PopenExecuteChildRaises(subprocess.Popen): + """Popen subclass for testing cleanup of subprocess.PIPE filehandles when + _execute_child fails. + """ + def _execute_child(self, *args, **kwargs): + raise PopenTestException("Forced Exception for Test") + + class ProcessTestCase(BaseTestCase): def test_call_seq(self): @@ -526,6 +538,7 @@ finally: for h in handles: os.close(h) + test_support.unlink(test_support.TESTFN) def test_list2cmdline(self): self.assertEqual(subprocess.list2cmdline(['a b c', 'd', 'e']), @@ -631,6 +644,27 @@ time.sleep(2) p.communicate("x" * 2**20) + # This test is Linux-ish specific for simplicity to at least have + # some coverage. It is not a platform specific bug. + @unittest.skipUnless(os.path.isdir('/proc/%d/fd' % os.getpid()), + "Linux specific") + def test_failed_child_execute_fd_leak(self): + """Test for the fork() failure fd leak reported in issue16327.""" + fd_directory = '/proc/%d/fd' % os.getpid() + fds_before_popen = os.listdir(fd_directory) + with self.assertRaises(PopenTestException): + PopenExecuteChildRaises( + [sys.executable, '-c', 'pass'], stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # NOTE: This test doesn't verify that the real _execute_child + # does not close the file descriptors itself on the way out + # during an exception. Code inspection has confirmed that. + + fds_after_exception = os.listdir(fd_directory) + self.assertEqual(fds_before_popen, fds_after_exception) + + # context manager class _SuppressCoreFiles(object): """Try to prevent core files from being created.""" @@ -717,6 +751,52 @@ self.addCleanup(p.stdout.close) self.assertEqual(p.stdout.read(), "apple") + class _TestExecuteChildPopen(subprocess.Popen): + """Used to test behavior at the end of _execute_child.""" + def __init__(self, testcase, *args, **kwargs): + self._testcase = testcase + subprocess.Popen.__init__(self, *args, **kwargs) + + def _execute_child( + self, args, executable, preexec_fn, close_fds, cwd, env, + universal_newlines, startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + try: + subprocess.Popen._execute_child( + self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + finally: + # Open a bunch of file descriptors and verify that + # none of them are the same as the ones the Popen + # instance is using for stdin/stdout/stderr. + devzero_fds = [os.open("/dev/zero", os.O_RDONLY) + for _ in range(8)] + try: + for fd in devzero_fds: + self._testcase.assertNotIn( + fd, (p2cwrite, c2pread, errread)) + finally: + map(os.close, devzero_fds) + + @unittest.skipIf(not os.path.exists("/dev/zero"), "/dev/zero required.") + def test_preexec_errpipe_does_not_double_close_pipes(self): + """Issue16140: Don't double close pipes on preexec error.""" + + def raise_it(): + raise RuntimeError("force the _execute_child() errpipe_data path.") + + with self.assertRaises(RuntimeError): + self._TestExecuteChildPopen( + self, [sys.executable, "-c", "pass"], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, preexec_fn=raise_it) + def test_args_string(self): # args is a string f, fname = mkstemp() @@ -812,6 +892,8 @@ getattr(p, method)(*args) return p + @unittest.skipIf(sys.platform.startswith(('netbsd', 'openbsd')), + "Due to known OS bug (issue #16762)") def _kill_dead_process(self, method, *args): # Do not inherit file handles from the parent. # It should fix failures on some platforms. diff --git a/lib-python/2.7/test/test_support.py b/lib-python/2.7/test/test_support.py --- a/lib-python/2.7/test/test_support.py +++ b/lib-python/2.7/test/test_support.py @@ -18,6 +18,9 @@ import UserDict import re import time +import struct +import _testcapi +import sysconfig try: import thread except ImportError: @@ -179,15 +182,79 @@ except KeyError: pass +if sys.platform.startswith("win"): + def _waitfor(func, pathname, waitall=False): + # Peform the operation + func(pathname) + # Now setup the wait loop + if waitall: + dirname = pathname + else: + dirname, name = os.path.split(pathname) + dirname = dirname or '.' + # Check for `pathname` to be removed from the filesystem. + # The exponential backoff of the timeout amounts to a total + # of ~1 second after which the deletion is probably an error + # anyway. + # Testing on a i7 at 4.3GHz shows that usually only 1 iteration is + # required when contention occurs. + timeout = 0.001 + while timeout < 1.0: + # Note we are only testing for the existance of the file(s) in + # the contents of the directory regardless of any security or + # access rights. If we have made it this far, we have sufficient + # permissions to do that much using Python's equivalent of the + # Windows API FindFirstFile. + # Other Windows APIs can fail or give incorrect results when + # dealing with files that are pending deletion. + L = os.listdir(dirname) + if not (L if waitall else name in L): + return + # Increase the timeout and try again + time.sleep(timeout) + timeout *= 2 + warnings.warn('tests may fail, delete still pending for ' + pathname, + RuntimeWarning, stacklevel=4) + + def _unlink(filename): + _waitfor(os.unlink, filename) + + def _rmdir(dirname): + _waitfor(os.rmdir, dirname) + + def _rmtree(path): + def _rmtree_inner(path): + for name in os.listdir(path): + fullname = os.path.join(path, name) + if os.path.isdir(fullname): + _waitfor(_rmtree_inner, fullname, waitall=True) + os.rmdir(fullname) + else: + os.unlink(fullname) + _waitfor(_rmtree_inner, path, waitall=True) + _waitfor(os.rmdir, path) +else: + _unlink = os.unlink + _rmdir = os.rmdir + _rmtree = shutil.rmtree + def unlink(filename): try: - os.unlink(filename) + _unlink(filename) except OSError: pass +def rmdir(dirname): + try: + _rmdir(dirname) + except OSError as error: + # The directory need not exist. + if error.errno != errno.ENOENT: + raise + def rmtree(path): try: - shutil.rmtree(path) + _rmtree(path) except OSError, e: # Unix returns ENOENT, Windows returns ESRCH. if e.errno not in (errno.ENOENT, errno.ESRCH): @@ -405,7 +472,7 @@ the CWD, an error is raised. If it's True, only a warning is raised and the original CWD is used. """ - if isinstance(name, unicode): + if have_unicode and isinstance(name, unicode): try: name = name.encode(sys.getfilesystemencoding() or 'ascii') except UnicodeEncodeError: @@ -767,6 +834,9 @@ ('EAI_FAIL', -4), ('EAI_NONAME', -2), ('EAI_NODATA', -5), + # Windows defines EAI_NODATA as 11001 but idiotic getaddrinfo() + # implementation actually returns WSANO_DATA i.e. 11004. + ('WSANO_DATA', 11004), ] denied = ResourceDenied("Resource '%s' is not available" % resource_name) @@ -858,6 +928,32 @@ gc.collect() +_header = '2P' +if hasattr(sys, "gettotalrefcount"): + _header = '2P' + _header +_vheader = _header + 'P' + +def calcobjsize(fmt): + return struct.calcsize(_header + fmt + '0P') + +def calcvobjsize(fmt): + return struct.calcsize(_vheader + fmt + '0P') + + +_TPFLAGS_HAVE_GC = 1<<14 +_TPFLAGS_HEAPTYPE = 1<<9 + +def check_sizeof(test, o, size): + result = sys.getsizeof(o) + # add GC header size + if ((type(o) == type) and (o.__flags__ & _TPFLAGS_HEAPTYPE) or\ + ((type(o) != type) and (type(o).__flags__ & _TPFLAGS_HAVE_GC))): + size += _testcapi.SIZEOF_PYGC_HEAD + msg = 'wrong size for %s: got %d, expected %d' \ + % (type(o), result, size) + test.assertEqual(result, size, msg) + + #======================================================================= # Decorator for running a function in a different locale, correctly resetting # it afterwards. @@ -966,7 +1062,7 @@ return wrapper return decorator -def precisionbigmemtest(size, memuse, overhead=5*_1M): +def precisionbigmemtest(size, memuse, overhead=5*_1M, dry_run=True): def decorator(f): def wrapper(self): if not real_max_memuse: @@ -974,11 +1070,12 @@ else: maxsize = size - if real_max_memuse and real_max_memuse < maxsize * memuse: - if verbose: - sys.stderr.write("Skipping %s because of memory " - "constraint\n" % (f.__name__,)) - return + if ((real_max_memuse or not dry_run) + and real_max_memuse < maxsize * memuse): + if verbose: + sys.stderr.write("Skipping %s because of memory " + "constraint\n" % (f.__name__,)) + return return f(self, maxsize) wrapper.size = size @@ -1093,6 +1190,16 @@ suite.addTest(unittest.makeSuite(cls)) _run_suite(suite) +#======================================================================= +# Check for the presence of docstrings. + +HAVE_DOCSTRINGS = (check_impl_detail(cpython=False) or + sys.platform == 'win32' or + sysconfig.get_config_var('WITH_DOC_STRINGS')) + +requires_docstrings = unittest.skipUnless(HAVE_DOCSTRINGS, + "test requires docstrings") + #======================================================================= # doctest driver. @@ -1192,6 +1299,33 @@ except: break + at contextlib.contextmanager +def swap_attr(obj, attr, new_val): + """Temporary swap out an attribute with a new object. + + Usage: + with swap_attr(obj, "attr", 5): + ... + + This will set obj.attr to 5 for the duration of the with: block, + restoring the old value at the end of the block. If `attr` doesn't + exist on `obj`, it will be created and then deleted at the end of the + block. + """ + if hasattr(obj, attr): + real_val = getattr(obj, attr) + setattr(obj, attr, new_val) + try: + yield + finally: + setattr(obj, attr, real_val) + else: + setattr(obj, attr, new_val) + try: + yield + finally: + delattr(obj, attr) + def py3k_bytes(b): """Emulate the py3k bytes() constructor. diff --git a/lib-python/2.7/test/test_sys.py b/lib-python/2.7/test/test_sys.py --- a/lib-python/2.7/test/test_sys.py +++ b/lib-python/2.7/test/test_sys.py @@ -490,22 +490,8 @@ class SizeofTest(unittest.TestCase): - TPFLAGS_HAVE_GC = 1<<14 - TPFLAGS_HEAPTYPE = 1L<<9 - def setUp(self): - self.c = len(struct.pack('c', ' ')) - self.H = len(struct.pack('H', 0)) - self.i = len(struct.pack('i', 0)) - self.l = len(struct.pack('l', 0)) - self.P = len(struct.pack('P', 0)) - # due to missing size_t information from struct, it is assumed that - # sizeof(Py_ssize_t) = sizeof(void*) - self.header = 'PP' - self.vheader = self.header + 'P' - if hasattr(sys, "gettotalrefcount"): - self.header += '2P' - self.vheader += '2P' + self.P = struct.calcsize('P') self.longdigit = sys.long_info.sizeof_digit import _testcapi self.gc_headsize = _testcapi.SIZEOF_PYGC_HEAD @@ -515,128 +501,109 @@ self.file.close() test.test_support.unlink(test.test_support.TESTFN) - def check_sizeof(self, o, size): - result = sys.getsizeof(o) - if ((type(o) == type) and (o.__flags__ & self.TPFLAGS_HEAPTYPE) or\ - ((type(o) != type) and (type(o).__flags__ & self.TPFLAGS_HAVE_GC))): - size += self.gc_headsize - msg = 'wrong size for %s: got %d, expected %d' \ - % (type(o), result, size) - self.assertEqual(result, size, msg) - - def calcsize(self, fmt): - """Wrapper around struct.calcsize which enforces the alignment of the - end of a structure to the alignment requirement of pointer. - - Note: This wrapper should only be used if a pointer member is included - and no member with a size larger than a pointer exists. - """ - return struct.calcsize(fmt + '0P') + check_sizeof = test.test_support.check_sizeof def test_gc_head_size(self): # Check that the gc header size is added to objects tracked by the gc. - h = self.header - size = self.calcsize + size = test.test_support.calcobjsize gc_header_size = self.gc_headsize # bool objects are not gc tracked - self.assertEqual(sys.getsizeof(True), size(h + 'l')) + self.assertEqual(sys.getsizeof(True), size('l')) # but lists are - self.assertEqual(sys.getsizeof([]), size(h + 'P PP') + gc_header_size) + self.assertEqual(sys.getsizeof([]), size('P PP') + gc_header_size) def test_default(self): - h = self.header - size = self.calcsize - self.assertEqual(sys.getsizeof(True, -1), size(h + 'l')) + size = test.test_support.calcobjsize + self.assertEqual(sys.getsizeof(True, -1), size('l')) def test_objecttypes(self): # check all types defined in Objects/ - h = self.header - vh = self.vheader - size = self.calcsize + size = test.test_support.calcobjsize + vsize = test.test_support.calcvobjsize check = self.check_sizeof # bool - check(True, size(h + 'l')) + check(True, size('l')) # buffer with test.test_support.check_py3k_warnings(): - check(buffer(''), size(h + '2P2Pil')) + check(buffer(''), size('2P2Pil')) # builtin_function_or_method - check(len, size(h + '3P')) + check(len, size('3P')) # bytearray samples = ['', 'u'*100000] for sample in samples: x = bytearray(sample) - check(x, size(vh + 'iPP') + x.__alloc__() * self.c) + check(x, vsize('iPP') + x.__alloc__()) # bytearray_iterator - check(iter(bytearray()), size(h + 'PP')) + check(iter(bytearray()), size('PP')) # cell def get_cell(): x = 42 def inner(): return x return inner - check(get_cell().func_closure[0], size(h + 'P')) + check(get_cell().func_closure[0], size('P')) # classobj (old-style class) class class_oldstyle(): def method(): pass - check(class_oldstyle, size(h + '7P')) + check(class_oldstyle, size('7P')) # instance (old-style class) - check(class_oldstyle(), size(h + '3P')) + check(class_oldstyle(), size('3P')) # instancemethod (old-style class) - check(class_oldstyle().method, size(h + '4P')) + check(class_oldstyle().method, size('4P')) # complex - check(complex(0,1), size(h + '2d')) + check(complex(0,1), size('2d')) # code - check(get_cell().func_code, size(h + '4i8Pi3P')) + check(get_cell().func_code, size('4i8Pi3P')) # BaseException - check(BaseException(), size(h + '3P')) + check(BaseException(), size('3P')) # UnicodeEncodeError - check(UnicodeEncodeError("", u"", 0, 0, ""), size(h + '5P2PP')) + check(UnicodeEncodeError("", u"", 0, 0, ""), size('5P2PP')) # UnicodeDecodeError - check(UnicodeDecodeError("", "", 0, 0, ""), size(h + '5P2PP')) + check(UnicodeDecodeError("", "", 0, 0, ""), size('5P2PP')) # UnicodeTranslateError - check(UnicodeTranslateError(u"", 0, 1, ""), size(h + '5P2PP')) + check(UnicodeTranslateError(u"", 0, 1, ""), size('5P2PP')) # method_descriptor (descriptor object) - check(str.lower, size(h + '2PP')) + check(str.lower, size('2PP')) # classmethod_descriptor (descriptor object) # XXX # member_descriptor (descriptor object) import datetime - check(datetime.timedelta.days, size(h + '2PP')) + check(datetime.timedelta.days, size('2PP')) # getset_descriptor (descriptor object) import __builtin__ - check(__builtin__.file.closed, size(h + '2PP')) + check(__builtin__.file.closed, size('2PP')) # wrapper_descriptor (descriptor object) - check(int.__add__, size(h + '2P2P')) + check(int.__add__, size('2P2P')) # dictproxy class C(object): pass - check(C.__dict__, size(h + 'P')) + check(C.__dict__, size('P')) # method-wrapper (descriptor object) - check({}.__iter__, size(h + '2P')) + check({}.__iter__, size('2P')) # dict - check({}, size(h + '3P2P' + 8*'P2P')) + check({}, size('3P2P' + 8*'P2P')) x = {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8} - check(x, size(h + '3P2P' + 8*'P2P') + 16*size('P2P')) + check(x, size('3P2P' + 8*'P2P') + 16*struct.calcsize('P2P')) # dictionary-keyiterator - check({}.iterkeys(), size(h + 'P2PPP')) + check({}.iterkeys(), size('P2PPP')) # dictionary-valueiterator - check({}.itervalues(), size(h + 'P2PPP')) + check({}.itervalues(), size('P2PPP')) # dictionary-itemiterator - check({}.iteritems(), size(h + 'P2PPP')) + check({}.iteritems(), size('P2PPP')) # ellipses - check(Ellipsis, size(h + '')) + check(Ellipsis, size('')) # EncodingMap import codecs, encodings.iso8859_3 x = codecs.charmap_build(encodings.iso8859_3.decoding_table) - check(x, size(h + '32B2iB')) + check(x, size('32B2iB')) # enumerate - check(enumerate([]), size(h + 'l3P')) + check(enumerate([]), size('l3P')) # file - check(self.file, size(h + '4P2i4P3i3P3i')) + check(self.file, size('4P2i4P3i3P3i')) # float - check(float(0), size(h + 'd')) + check(float(0), size('d')) # sys.floatinfo - check(sys.float_info, size(vh) + self.P * len(sys.float_info)) + check(sys.float_info, vsize('') + self.P * len(sys.float_info)) # frame import inspect CO_MAXBLOCKS = 20 @@ -645,10 +612,10 @@ nfrees = len(x.f_code.co_freevars) extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\ ncells + nfrees - 1 - check(x, size(vh + '12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) + check(x, vsize('12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) # function def func(): pass - check(func, size(h + '9P')) + check(func, size('9P')) class c(): @staticmethod def foo(): @@ -657,65 +624,65 @@ def bar(cls): pass # staticmethod - check(foo, size(h + 'P')) + check(foo, size('P')) # classmethod - check(bar, size(h + 'P')) + check(bar, size('P')) # generator def get_gen(): yield 1 - check(get_gen(), size(h + 'Pi2P')) + check(get_gen(), size('Pi2P')) # integer - check(1, size(h + 'l')) - check(100, size(h + 'l')) + check(1, size('l')) + check(100, size('l')) # iterator - check(iter('abc'), size(h + 'lP')) + check(iter('abc'), size('lP')) # callable-iterator import re - check(re.finditer('',''), size(h + '2P')) + check(re.finditer('',''), size('2P')) # list samples = [[], [1,2,3], ['1', '2', '3']] for sample in samples: - check(sample, size(vh + 'PP') + len(sample)*self.P) + check(sample, vsize('PP') + len(sample)*self.P) # sortwrapper (list) # XXX # cmpwrapper (list) # XXX # listiterator (list) - check(iter([]), size(h + 'lP')) + check(iter([]), size('lP')) # listreverseiterator (list) - check(reversed([]), size(h + 'lP')) + check(reversed([]), size('lP')) # long - check(0L, size(vh)) - check(1L, size(vh) + self.longdigit) - check(-1L, size(vh) + self.longdigit) + check(0L, vsize('')) + check(1L, vsize('') + self.longdigit) + check(-1L, vsize('') + self.longdigit) PyLong_BASE = 2**sys.long_info.bits_per_digit - check(long(PyLong_BASE), size(vh) + 2*self.longdigit) - check(long(PyLong_BASE**2-1), size(vh) + 2*self.longdigit) - check(long(PyLong_BASE**2), size(vh) + 3*self.longdigit) + check(long(PyLong_BASE), vsize('') + 2*self.longdigit) + check(long(PyLong_BASE**2-1), vsize('') + 2*self.longdigit) + check(long(PyLong_BASE**2), vsize('') + 3*self.longdigit) # module - check(unittest, size(h + 'P')) + check(unittest, size('P')) # None - check(None, size(h + '')) + check(None, size('')) # object - check(object(), size(h + '')) + check(object(), size('')) # property (descriptor object) class C(object): def getx(self): return self.__x def setx(self, value): self.__x = value def delx(self): del self.__x x = property(getx, setx, delx, "") - check(x, size(h + '4Pi')) + check(x, size('4Pi')) # PyCObject # PyCapsule # XXX # rangeiterator - check(iter(xrange(1)), size(h + '4l')) + check(iter(xrange(1)), size('4l')) # reverse - check(reversed(''), size(h + 'PP')) + check(reversed(''), size('PP')) # set # frozenset PySet_MINSIZE = 8 samples = [[], range(10), range(50)] - s = size(h + '3P2P' + PySet_MINSIZE*'lP' + 'lP') + s = size('3P2P' + PySet_MINSIZE*'lP' + 'lP') for sample in samples: minused = len(sample) if minused == 0: tmp = 1 @@ -732,23 +699,24 @@ check(set(sample), s + newsize*struct.calcsize('lP')) check(frozenset(sample), s + newsize*struct.calcsize('lP')) # setiterator - check(iter(set()), size(h + 'P3P')) + check(iter(set()), size('P3P')) # slice - check(slice(1), size(h + '3P')) + check(slice(1), size('3P')) # str - check('', struct.calcsize(vh + 'li') + 1) - check('abc', struct.calcsize(vh + 'li') + 1 + 3*self.c) + vh = test.test_support._vheader + check('', struct.calcsize(vh + 'lic')) + check('abc', struct.calcsize(vh + 'lic') + 3) # super - check(super(int), size(h + '3P')) + check(super(int), size('3P')) # tuple - check((), size(vh)) - check((1,2,3), size(vh) + 3*self.P) + check((), vsize('')) + check((1,2,3), vsize('') + 3*self.P) # tupleiterator - check(iter(()), size(h + 'lP')) + check(iter(()), size('lP')) # type # (PyTypeObject + PyNumberMethods + PyMappingMethods + # PySequenceMethods + PyBufferProcs) - s = size(vh + 'P2P15Pl4PP9PP11PI') + size('41P 10P 3P 6P') + s = vsize('P2P15Pl4PP9PP11PI') + struct.calcsize('41P 10P 3P 6P') class newstyleclass(object): pass check(newstyleclass, s) @@ -763,41 +731,40 @@ # we need to test for both sizes, because we don't know if the string # has been cached for s in samples: - check(s, size(h + 'PPlP') + usize * (len(s) + 1)) + check(s, size('PPlP') + usize * (len(s) + 1)) # weakref import weakref - check(weakref.ref(int), size(h + '2Pl2P')) + check(weakref.ref(int), size('2Pl2P')) # weakproxy # XXX # weakcallableproxy - check(weakref.proxy(int), size(h + '2Pl2P')) + check(weakref.proxy(int), size('2Pl2P')) # xrange - check(xrange(1), size(h + '3l')) - check(xrange(66000), size(h + '3l')) + check(xrange(1), size('3l')) + check(xrange(66000), size('3l')) def test_pythontypes(self): # check all types defined in Python/ - h = self.header - vh = self.vheader - size = self.calcsize + size = test.test_support.calcobjsize + vsize = test.test_support.calcvobjsize check = self.check_sizeof # _ast.AST import _ast - check(_ast.AST(), size(h + '')) + check(_ast.AST(), size('')) # imp.NullImporter import imp - check(imp.NullImporter(self.file.name), size(h + '')) + check(imp.NullImporter(self.file.name), size('')) try: raise TypeError except TypeError: tb = sys.exc_info()[2] # traceback if tb != None: - check(tb, size(h + '2P2i')) + check(tb, size('2P2i')) # symtable entry # XXX # sys.flags - check(sys.flags, size(vh) + self.P * len(sys.flags)) + check(sys.flags, vsize('') + self.P * len(sys.flags)) def test_main(): diff --git a/lib-python/2.7/test/test_sys_settrace.py b/lib-python/2.7/test/test_sys_settrace.py --- a/lib-python/2.7/test/test_sys_settrace.py +++ b/lib-python/2.7/test/test_sys_settrace.py @@ -417,7 +417,7 @@ except ValueError: pass else: - self.fail("exception not thrown!") + self.fail("exception not raised!") except RuntimeError: self.fail("recursion counter not reset") @@ -670,6 +670,14 @@ no_jump_to_non_integers.jump = (2, "Spam") no_jump_to_non_integers.output = [True] +def jump_across_with(output): + with open(test_support.TESTFN, "wb") as fp: + pass + with open(test_support.TESTFN, "wb") as fp: + pass +jump_across_with.jump = (1, 3) +jump_across_with.output = [] + # This verifies that you can't set f_lineno via _getframe or similar # trickery. def no_jump_without_trace_function(): @@ -739,6 +747,9 @@ self.run_test(no_jump_to_non_integers) def test_19_no_jump_without_trace_function(self): no_jump_without_trace_function() + def test_jump_across_with(self): + self.addCleanup(test_support.unlink, test_support.TESTFN) + self.run_test(jump_across_with) def test_20_large_function(self): d = {} diff --git a/lib-python/2.7/test/test_sysconfig.py b/lib-python/2.7/test/test_sysconfig.py --- a/lib-python/2.7/test/test_sysconfig.py +++ b/lib-python/2.7/test/test_sysconfig.py @@ -14,6 +14,7 @@ get_path, get_path_names, _INSTALL_SCHEMES, _get_default_scheme, _expand_vars, get_scheme_names, get_config_var) +import _osx_support class TestSysConfig(unittest.TestCase): @@ -137,6 +138,7 @@ ('Darwin Kernel Version 8.11.1: ' 'Wed Oct 10 18:23:28 PDT 2007; ' 'root:xnu-792.25.20~1/RELEASE_I386'), 'PowerPC')) + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3' get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g ' @@ -156,6 +158,7 @@ ('Darwin Kernel Version 8.11.1: ' 'Wed Oct 10 18:23:28 PDT 2007; ' 'root:xnu-792.25.20~1/RELEASE_I386'), 'i386')) + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3' get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g ' @@ -171,6 +174,7 @@ sys.maxint = maxint # macbook with fat binaries (fat, universal or fat64) + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.4' get_config_vars()['CFLAGS'] = ('-arch ppc -arch i386 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' @@ -179,6 +183,7 @@ self.assertEqual(get_platform(), 'macosx-10.4-fat') + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch i386 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' @@ -186,18 +191,21 @@ self.assertEqual(get_platform(), 'macosx-10.4-intel') + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc -arch i386 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' '-dynamic -DNDEBUG -g -O3') self.assertEqual(get_platform(), 'macosx-10.4-fat3') + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch ppc64 -arch x86_64 -arch ppc -arch i386 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' '-dynamic -DNDEBUG -g -O3') self.assertEqual(get_platform(), 'macosx-10.4-universal') + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc64 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' @@ -206,6 +214,7 @@ self.assertEqual(get_platform(), 'macosx-10.4-fat64') for arch in ('ppc', 'i386', 'x86_64', 'ppc64'): + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch %s -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' diff --git a/lib-python/2.7/test/test_tarfile.py b/lib-python/2.7/test/test_tarfile.py --- a/lib-python/2.7/test/test_tarfile.py +++ b/lib-python/2.7/test/test_tarfile.py @@ -154,6 +154,9 @@ def test_fileobj_symlink2(self): self._test_fileobj_link("./ustar/linktest2/symtype", "ustar/linktest1/regtype") + def test_issue14160(self): + self._test_fileobj_link("symtype2", "ustar/regtype") + class CommonReadTest(ReadTest): @@ -294,26 +297,21 @@ def test_extract_hardlink(self): # Test hardlink extraction (e.g. bug #857297). - tar = tarfile.open(tarname, errorlevel=1, encoding="iso8859-1") + with tarfile.open(tarname, errorlevel=1, encoding="iso8859-1") as tar: + tar.extract("ustar/regtype", TEMPDIR) + self.addCleanup(os.remove, os.path.join(TEMPDIR, "ustar/regtype")) - tar.extract("ustar/regtype", TEMPDIR) - try: tar.extract("ustar/lnktype", TEMPDIR) - except EnvironmentError, e: - if e.errno == errno.ENOENT: - self.fail("hardlink not extracted properly") + self.addCleanup(os.remove, os.path.join(TEMPDIR, "ustar/lnktype")) + with open(os.path.join(TEMPDIR, "ustar/lnktype"), "rb") as f: + data = f.read() + self.assertEqual(md5sum(data), md5_regtype) - data = open(os.path.join(TEMPDIR, "ustar/lnktype"), "rb").read() - self.assertEqual(md5sum(data), md5_regtype) - - try: tar.extract("ustar/symtype", TEMPDIR) - except EnvironmentError, e: - if e.errno == errno.ENOENT: - self.fail("symlink not extracted properly") - - data = open(os.path.join(TEMPDIR, "ustar/symtype"), "rb").read() - self.assertEqual(md5sum(data), md5_regtype) + self.addCleanup(os.remove, os.path.join(TEMPDIR, "ustar/symtype")) + with open(os.path.join(TEMPDIR, "ustar/symtype"), "rb") as f: + data = f.read() + self.assertEqual(md5sum(data), md5_regtype) def test_extractall(self): # Test if extractall() correctly restores directory permissions @@ -855,7 +853,7 @@ tar = tarfile.open(tmpname, "r") for t in tar: - self.assert_(t.name == "." or t.name.startswith("./")) + self.assertTrue(t.name == "." or t.name.startswith("./")) tar.close() finally: os.chdir(cwd) diff --git a/lib-python/2.7/test/test_tcl.py b/lib-python/2.7/test/test_tcl.py --- a/lib-python/2.7/test/test_tcl.py +++ b/lib-python/2.7/test/test_tcl.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import unittest +import sys import os from test import test_support @@ -151,6 +152,27 @@ # exit code must be zero self.assertEqual(f.close(), None) + def test_passing_values(self): + def passValue(value): + return self.interp.call('set', '_', value) + self.assertEqual(passValue(True), True) + self.assertEqual(passValue(False), False) + self.assertEqual(passValue('string'), 'string') + self.assertEqual(passValue('string\u20ac'), 'string\u20ac') + self.assertEqual(passValue(u'string'), u'string') + self.assertEqual(passValue(u'string\u20ac'), u'string\u20ac') + for i in (0, 1, -1, int(2**31-1), int(-2**31)): + self.assertEqual(passValue(i), i) + for f in (0.0, 1.0, -1.0, 1//3, 1/3.0, + sys.float_info.min, sys.float_info.max, + -sys.float_info.min, -sys.float_info.max): + self.assertEqual(passValue(f), f) + for f in float('nan'), float('inf'), -float('inf'): + if f != f: # NaN + self.assertNotEqual(passValue(f), f) + else: + self.assertEqual(passValue(f), f) + self.assertEqual(passValue((1, '2', (3.4,))), (1, '2', (3.4,))) def test_main(): diff --git a/lib-python/2.7/test/test_telnetlib.py b/lib-python/2.7/test/test_telnetlib.py --- a/lib-python/2.7/test/test_telnetlib.py +++ b/lib-python/2.7/test/test_telnetlib.py @@ -3,6 +3,7 @@ import time import Queue +import unittest from unittest import TestCase from test import test_support threading = test_support.import_module('threading') @@ -135,6 +136,28 @@ self.assertEqual(data, want[0]) self.assertEqual(telnet.read_all(), 'not seen') + def test_read_until_with_poll(self): + """Use select.poll() to implement telnet.read_until().""" + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + if not telnet._has_poll: + raise unittest.SkipTest('select.poll() is required') + telnet._has_poll = True + self.dataq.join() + data = telnet.read_until('match') + self.assertEqual(data, ''.join(want[:-2])) + + def test_read_until_with_select(self): + """Use select.select() to implement telnet.read_until().""" + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + telnet._has_poll = False + self.dataq.join() + data = telnet.read_until('match') + self.assertEqual(data, ''.join(want[:-2])) + def test_read_all_A(self): """ read_all() @@ -357,8 +380,75 @@ self.assertEqual('', telnet.read_sb_data()) nego.sb_getter = None # break the nego => telnet cycle + +class ExpectTests(TestCase): + def setUp(self): + self.evt = threading.Event() + self.dataq = Queue.Queue() + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.settimeout(10) + self.port = test_support.bind_port(self.sock) + self.thread = threading.Thread(target=server, args=(self.evt,self.sock, + self.dataq)) + self.thread.start() + self.evt.wait() + + def tearDown(self): + self.thread.join() + + # use a similar approach to testing timeouts as test_timeout.py + # these will never pass 100% but make the fuzz big enough that it is rare + block_long = 0.6 + block_short = 0.3 + def test_expect_A(self): + """ + expect(expected, [timeout]) + Read until the expected string has been seen, or a timeout is + hit (default is no timeout); may block. + """ + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + self.dataq.join() + (_,_,data) = telnet.expect(['match']) + self.assertEqual(data, ''.join(want[:-2])) + + def test_expect_B(self): + # test the timeout - it does NOT raise socket.timeout + want = ['hello', self.block_long, 'not seen', EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + self.dataq.join() + (_,_,data) = telnet.expect(['not seen'], self.block_short) + self.assertEqual(data, want[0]) + self.assertEqual(telnet.read_all(), 'not seen') + + def test_expect_with_poll(self): + """Use select.poll() to implement telnet.expect().""" + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + if not telnet._has_poll: + raise unittest.SkipTest('select.poll() is required') + telnet._has_poll = True + self.dataq.join() + (_,_,data) = telnet.expect(['match']) + self.assertEqual(data, ''.join(want[:-2])) + + def test_expect_with_select(self): + """Use select.select() to implement telnet.expect().""" + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + telnet._has_poll = False + self.dataq.join() + (_,_,data) = telnet.expect(['match']) + self.assertEqual(data, ''.join(want[:-2])) + + def test_main(verbose=None): - test_support.run_unittest(GeneralTests, ReadTests, OptionTests) + test_support.run_unittest(GeneralTests, ReadTests, OptionTests, + ExpectTests) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_tempfile.py b/lib-python/2.7/test/test_tempfile.py --- a/lib-python/2.7/test/test_tempfile.py +++ b/lib-python/2.7/test/test_tempfile.py @@ -1,13 +1,16 @@ # tempfile.py unit tests. import tempfile +import errno +import io import os import signal +import shutil import sys import re import warnings import unittest -from test import test_support +from test import test_support as support warnings.filterwarnings("ignore", category=RuntimeWarning, @@ -177,7 +180,7 @@ # _candidate_tempdir_list contains the expected directories # Make sure the interesting environment variables are all set. - with test_support.EnvironmentVarGuard() as env: + with support.EnvironmentVarGuard() as env: for envname in 'TMPDIR', 'TEMP', 'TMP': dirname = os.getenv(envname) if not dirname: @@ -202,8 +205,51 @@ test_classes.append(test__candidate_tempdir_list) +# We test _get_default_tempdir some more by testing gettempdir. -# We test _get_default_tempdir by testing gettempdir. +class TestGetDefaultTempdir(TC): + """Test _get_default_tempdir().""" + + def test_no_files_left_behind(self): + # use a private empty directory + our_temp_directory = tempfile.mkdtemp() + try: + # force _get_default_tempdir() to consider our empty directory + def our_candidate_list(): + return [our_temp_directory] + + with support.swap_attr(tempfile, "_candidate_tempdir_list", + our_candidate_list): + # verify our directory is empty after _get_default_tempdir() + tempfile._get_default_tempdir() + self.assertEqual(os.listdir(our_temp_directory), []) + + def raise_OSError(*args, **kwargs): + raise OSError(-1) + + with support.swap_attr(io, "open", raise_OSError): + # test again with failing io.open() + with self.assertRaises(IOError) as cm: + tempfile._get_default_tempdir() + self.assertEqual(cm.exception.errno, errno.ENOENT) + self.assertEqual(os.listdir(our_temp_directory), []) + + open = io.open + def bad_writer(*args, **kwargs): + fp = open(*args, **kwargs) + fp.write = raise_OSError + return fp + + with support.swap_attr(io, "open", bad_writer): + # test again with failing write() + with self.assertRaises(IOError) as cm: + tempfile._get_default_tempdir() + self.assertEqual(cm.exception.errno, errno.ENOENT) + self.assertEqual(os.listdir(our_temp_directory), []) + finally: + shutil.rmtree(our_temp_directory) + +test_classes.append(TestGetDefaultTempdir) class test__get_candidate_names(TC): @@ -299,7 +345,7 @@ if not has_spawnl: return # ugh, can't use SkipTest. - if test_support.verbose: + if support.verbose: v="v" else: v="q" @@ -738,6 +784,17 @@ f.write(b'x') self.assertTrue(f._rolled) + def test_xreadlines(self): + f = self.do_create(max_size=20) + f.write(b'abc\n' * 5) + f.seek(0) + self.assertFalse(f._rolled) + self.assertEqual(list(f.xreadlines()), [b'abc\n'] * 5) + f.write(b'x\ny') + self.assertTrue(f._rolled) + f.seek(0) + self.assertEqual(list(f.xreadlines()), [b'abc\n'] * 5 + [b'x\n', b'y']) + def test_sparse(self): # A SpooledTemporaryFile that is written late in the file will extend # when that occurs @@ -793,6 +850,26 @@ seek(0, 0) self.assertTrue(read(70) == 'a'*35 + 'b'*35) + def test_properties(self): + f = tempfile.SpooledTemporaryFile(max_size=10) + f.write(b'x' * 10) + self.assertFalse(f._rolled) + self.assertEqual(f.mode, 'w+b') + self.assertIsNone(f.name) + with self.assertRaises(AttributeError): + f.newlines + with self.assertRaises(AttributeError): + f.encoding + + f.write(b'x') + self.assertTrue(f._rolled) + self.assertEqual(f.mode, 'w+b') + self.assertIsNotNone(f.name) + with self.assertRaises(AttributeError): + f.newlines + with self.assertRaises(AttributeError): + f.encoding + def test_context_manager_before_rollover(self): # A SpooledTemporaryFile can be used as a context manager with tempfile.SpooledTemporaryFile(max_size=1) as f: @@ -882,7 +959,7 @@ test_classes.append(test_TemporaryFile) def test_main(): - test_support.run_unittest(*test_classes) + support.run_unittest(*test_classes) if __name__ == "__main__": test_main() diff --git a/lib-python/2.7/test/test_textwrap.py b/lib-python/2.7/test/test_textwrap.py --- a/lib-python/2.7/test/test_textwrap.py +++ b/lib-python/2.7/test/test_textwrap.py @@ -66,6 +66,15 @@ "I'm glad to hear it!"]) self.check_wrap(text, 80, [text]) + def test_empty_string(self): + # Check that wrapping the empty string returns an empty list. + self.check_wrap("", 6, []) + self.check_wrap("", 6, [], drop_whitespace=False) + + def test_empty_string_with_initial_indent(self): + # Check that the empty string is not indented. + self.check_wrap("", 6, [], initial_indent="++") + self.check_wrap("", 6, [], initial_indent="++", drop_whitespace=False) def test_whitespace(self): # Whitespace munging and end-of-sentence detection @@ -323,7 +332,32 @@ ["blah", " ", "(ding", " ", "dong),", " ", "wubba"]) - def test_initial_whitespace(self): + def test_drop_whitespace_false(self): + # Check that drop_whitespace=False preserves whitespace. + # SF patch #1581073 + text = " This is a sentence with much whitespace." + self.check_wrap(text, 10, + [" This is a", " ", "sentence ", + "with ", "much white", "space."], + drop_whitespace=False) + + def test_drop_whitespace_false_whitespace_only(self): + # Check that drop_whitespace=False preserves a whitespace-only string. + self.check_wrap(" ", 6, [" "], drop_whitespace=False) + + def test_drop_whitespace_false_whitespace_only_with_indent(self): + # Check that a whitespace-only string gets indented (when + # drop_whitespace is False). + self.check_wrap(" ", 6, [" "], drop_whitespace=False, + initial_indent=" ") + + def test_drop_whitespace_whitespace_only(self): + # Check drop_whitespace on a whitespace-only string. + self.check_wrap(" ", 6, []) + + def test_drop_whitespace_leading_whitespace(self): + # Check that drop_whitespace does not drop leading whitespace (if + # followed by non-whitespace). # SF bug #622849 reported inconsistent handling of leading # whitespace; let's test that a bit, shall we? text = " This is a sentence with leading whitespace." @@ -332,13 +366,27 @@ self.check_wrap(text, 30, [" This is a sentence with", "leading whitespace."]) - def test_no_drop_whitespace(self): - # SF patch #1581073 - text = " This is a sentence with much whitespace." - self.check_wrap(text, 10, - [" This is a", " ", "sentence ", - "with ", "much white", "space."], + def test_drop_whitespace_whitespace_line(self): + # Check that drop_whitespace skips the whole line if a non-leading + # line consists only of whitespace. + text = "abcd efgh" + # Include the result for drop_whitespace=False for comparison. + self.check_wrap(text, 6, ["abcd", " ", "efgh"], drop_whitespace=False) + self.check_wrap(text, 6, ["abcd", "efgh"]) + + def test_drop_whitespace_whitespace_only_with_indent(self): + # Check that initial_indent is not applied to a whitespace-only + # string. This checks a special case of the fact that dropping + # whitespace occurs before indenting. + self.check_wrap(" ", 6, [], initial_indent="++") + + def test_drop_whitespace_whitespace_indent(self): + # Check that drop_whitespace does not drop whitespace indents. + # This checks a special case of the fact that dropping whitespace + # occurs before indenting. + self.check_wrap("abcd efgh", 6, [" abcd", " efgh"], + initial_indent=" ", subsequent_indent=" ") if test_support.have_unicode: def test_unicode(self): diff --git a/lib-python/2.7/test/test_thread.py b/lib-python/2.7/test/test_thread.py --- a/lib-python/2.7/test/test_thread.py +++ b/lib-python/2.7/test/test_thread.py @@ -130,6 +130,29 @@ time.sleep(0.01) self.assertEqual(thread._count(), orig) + def test_save_exception_state_on_error(self): + # See issue #14474 + def task(): + started.release() + raise SyntaxError + def mywrite(self, *args): + try: + raise ValueError + except ValueError: + pass + real_write(self, *args) + c = thread._count() + started = thread.allocate_lock() + with test_support.captured_output("stderr") as stderr: + real_write = stderr.write + stderr.write = mywrite + started.acquire() + thread.start_new_thread(task, ()) + started.acquire() + while thread._count() > c: + time.sleep(0.01) + self.assertIn("Traceback", stderr.getvalue()) + class Barrier: def __init__(self, num_threads): diff --git a/lib-python/2.7/test/test_threading.py b/lib-python/2.7/test/test_threading.py --- a/lib-python/2.7/test/test_threading.py +++ b/lib-python/2.7/test/test_threading.py @@ -2,6 +2,8 @@ import test.test_support from test.test_support import verbose +from test.script_helper import assert_python_ok + import random import re import sys @@ -414,6 +416,33 @@ msg=('%d references still around' % sys.getrefcount(weak_raising_cyclic_object()))) + @unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()') + def test_dummy_thread_after_fork(self): + # Issue #14308: a dummy thread in the active list doesn't mess up + # the after-fork mechanism. + code = """if 1: + import thread, threading, os, time + + def background_thread(evt): + # Creates and registers the _DummyThread instance + threading.current_thread() + evt.set() + time.sleep(10) + + evt = threading.Event() + thread.start_new_thread(background_thread, (evt,)) + evt.wait() + assert threading.active_count() == 2, threading.active_count() + if os.fork() == 0: + assert threading.active_count() == 1, threading.active_count() + os._exit(0) + else: + os.wait() + """ + _, out, err = assert_python_ok("-c", code) + self.assertEqual(out, '') + self.assertEqual(err, '') + class ThreadJoinOnShutdown(BaseTestCase): diff --git a/lib-python/2.7/test/test_time.py b/lib-python/2.7/test/test_time.py --- a/lib-python/2.7/test/test_time.py +++ b/lib-python/2.7/test/test_time.py @@ -106,7 +106,7 @@ def test_strptime(self): # Should be able to go round-trip from strftime to strptime without - # throwing an exception. + # raising an exception. tt = time.gmtime(self.t) for directive in ('a', 'A', 'b', 'B', 'c', 'd', 'H', 'I', 'j', 'm', 'M', 'p', 'S', diff --git a/lib-python/2.7/test/test_tokenize.py b/lib-python/2.7/test/test_tokenize.py --- a/lib-python/2.7/test/test_tokenize.py +++ b/lib-python/2.7/test/test_tokenize.py @@ -278,6 +278,31 @@ OP '+' (1, 32) (1, 33) STRING 'UR"ABC"' (1, 34) (1, 41) + >>> dump_tokens("b'abc' + B'abc'") + STRING "b'abc'" (1, 0) (1, 6) + OP '+' (1, 7) (1, 8) + STRING "B'abc'" (1, 9) (1, 15) + >>> dump_tokens('b"abc" + B"abc"') + STRING 'b"abc"' (1, 0) (1, 6) + OP '+' (1, 7) (1, 8) + STRING 'B"abc"' (1, 9) (1, 15) + >>> dump_tokens("br'abc' + bR'abc' + Br'abc' + BR'abc'") + STRING "br'abc'" (1, 0) (1, 7) + OP '+' (1, 8) (1, 9) + STRING "bR'abc'" (1, 10) (1, 17) + OP '+' (1, 18) (1, 19) + STRING "Br'abc'" (1, 20) (1, 27) + OP '+' (1, 28) (1, 29) + STRING "BR'abc'" (1, 30) (1, 37) + >>> dump_tokens('br"abc" + bR"abc" + Br"abc" + BR"abc"') + STRING 'br"abc"' (1, 0) (1, 7) + OP '+' (1, 8) (1, 9) + STRING 'bR"abc"' (1, 10) (1, 17) + OP '+' (1, 18) (1, 19) + STRING 'Br"abc"' (1, 20) (1, 27) + OP '+' (1, 28) (1, 29) + STRING 'BR"abc"' (1, 30) (1, 37) + Operators >>> dump_tokens("def d22(a, b, c=2, d=2, *k): pass") @@ -525,6 +550,10 @@ NAME 'pass' (3, 9) (3, 13) DEDENT '' (4, 0) (4, 0) DEDENT '' (4, 0) (4, 0) + +Pathological whitespace (http://bugs.python.org/issue16152) + >>> dump_tokens("@ ") + OP '@' (1, 0) (1, 1) """ diff --git a/lib-python/2.7/test/test_tools.py b/lib-python/2.7/test/test_tools.py --- a/lib-python/2.7/test/test_tools.py +++ b/lib-python/2.7/test/test_tools.py @@ -5,22 +5,28 @@ """ import os +import sys import unittest +import shutil +import subprocess import sysconfig +import tempfile +import textwrap from test import test_support -from test.script_helper import assert_python_ok +from test.script_helper import assert_python_ok, temp_dir if not sysconfig.is_python_build(): # XXX some installers do contain the tools, should we detect that # and run the tests in that case too? raise unittest.SkipTest('test irrelevant for an installed Python') -srcdir = sysconfig.get_config_var('projectbase') -basepath = os.path.join(os.getcwd(), srcdir, 'Tools') +basepath = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + 'Tools') +scriptsdir = os.path.join(basepath, 'scripts') class ReindentTests(unittest.TestCase): - script = os.path.join(basepath, 'scripts', 'reindent.py') + script = os.path.join(scriptsdir, 'reindent.py') def test_noargs(self): assert_python_ok(self.script) @@ -31,8 +37,331 @@ self.assertGreater(err, b'') +class PindentTests(unittest.TestCase): + script = os.path.join(scriptsdir, 'pindent.py') + + def assertFileEqual(self, fn1, fn2): + with open(fn1) as f1, open(fn2) as f2: + self.assertEqual(f1.readlines(), f2.readlines()) + + def pindent(self, source, *args): + proc = subprocess.Popen( + (sys.executable, self.script) + args, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + universal_newlines=True) + out, err = proc.communicate(source) + self.assertIsNone(err) + return out + + def lstriplines(self, data): + return '\n'.join(line.lstrip() for line in data.splitlines()) + '\n' + + def test_selftest(self): + self.maxDiff = None + with temp_dir() as directory: + data_path = os.path.join(directory, '_test.py') + with open(self.script) as f: + closed = f.read() + with open(data_path, 'w') as f: + f.write(closed) + + rc, out, err = assert_python_ok(self.script, '-d', data_path) + self.assertEqual(out, b'') + self.assertEqual(err, b'') + backup = data_path + '~' + self.assertTrue(os.path.exists(backup)) + with open(backup) as f: + self.assertEqual(f.read(), closed) + with open(data_path) as f: + clean = f.read() + compile(clean, '_test.py', 'exec') + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + + rc, out, err = assert_python_ok(self.script, '-c', data_path) + self.assertEqual(out, b'') + self.assertEqual(err, b'') + with open(backup) as f: + self.assertEqual(f.read(), clean) + with open(data_path) as f: + self.assertEqual(f.read(), closed) + + broken = self.lstriplines(closed) + with open(data_path, 'w') as f: + f.write(broken) + rc, out, err = assert_python_ok(self.script, '-r', data_path) + self.assertEqual(out, b'') + self.assertEqual(err, b'') + with open(backup) as f: + self.assertEqual(f.read(), broken) + with open(data_path) as f: + indented = f.read() + compile(indented, '_test.py', 'exec') + self.assertEqual(self.pindent(broken, '-r'), indented) + + def pindent_test(self, clean, closed): + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + broken = self.lstriplines(closed) + self.assertEqual(self.pindent(broken, '-r', '-e', '-s', '4'), closed) + + def test_statements(self): + clean = textwrap.dedent("""\ + if a: + pass + + if a: + pass + else: + pass + + if a: + pass + elif: + pass + else: + pass + + while a: + break + + while a: + break + else: + pass + + for i in a: + break + + for i in a: + break + else: + pass + + try: + pass + finally: + pass + + try: + pass + except TypeError: + pass + except ValueError: + pass + else: + pass + + try: + pass + except TypeError: + pass + except ValueError: + pass + finally: + pass + + with a: + pass + + class A: + pass + + def f(): + pass + """) + + closed = textwrap.dedent("""\ + if a: + pass + # end if + + if a: + pass + else: + pass + # end if + + if a: + pass + elif: + pass + else: + pass + # end if + + while a: + break + # end while + + while a: + break + else: + pass + # end while + + for i in a: + break + # end for + + for i in a: + break + else: + pass + # end for + + try: + pass + finally: + pass + # end try + + try: + pass + except TypeError: + pass + except ValueError: + pass + else: + pass + # end try + + try: + pass + except TypeError: + pass + except ValueError: + pass + finally: + pass + # end try + + with a: + pass + # end with + + class A: + pass + # end class A + + def f(): + pass + # end def f + """) + self.pindent_test(clean, closed) + + def test_multilevel(self): + clean = textwrap.dedent("""\ + def foobar(a, b): + if a == b: + a = a+1 + elif a < b: + b = b-1 + if b > a: a = a-1 + else: + print 'oops!' + """) + closed = textwrap.dedent("""\ + def foobar(a, b): + if a == b: + a = a+1 + elif a < b: + b = b-1 + if b > a: a = a-1 + # end if + else: + print 'oops!' + # end if + # end def foobar + """) + self.pindent_test(clean, closed) + + def test_preserve_indents(self): + clean = textwrap.dedent("""\ + if a: + if b: + pass + """) + closed = textwrap.dedent("""\ + if a: + if b: + pass + # end if + # end if + """) + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + broken = self.lstriplines(closed) + self.assertEqual(self.pindent(broken, '-r', '-e', '-s', '9'), closed) + clean = textwrap.dedent("""\ + if a: + \tif b: + \t\tpass + """) + closed = textwrap.dedent("""\ + if a: + \tif b: + \t\tpass + \t# end if + # end if + """) + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + broken = self.lstriplines(closed) + self.assertEqual(self.pindent(broken, '-r'), closed) + + def test_escaped_newline(self): + clean = textwrap.dedent("""\ + class\\ + \\ + A: + def\ + \\ + f: + pass + """) + closed = textwrap.dedent("""\ + class\\ + \\ + A: + def\ + \\ + f: + pass + # end def f + # end class A + """) + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + + def test_empty_line(self): + clean = textwrap.dedent("""\ + if a: + + pass + """) + closed = textwrap.dedent("""\ + if a: + + pass + # end if + """) + self.pindent_test(clean, closed) + + def test_oneline(self): + clean = textwrap.dedent("""\ + if a: pass + """) + closed = textwrap.dedent("""\ + if a: pass + # end if + """) + self.pindent_test(clean, closed) + + def test_main(): - test_support.run_unittest(ReindentTests) + test_support.run_unittest(*[obj for obj in globals().values() + if isinstance(obj, type)]) if __name__ == '__main__': diff --git a/lib-python/2.7/test/test_ucn.py b/lib-python/2.7/test/test_ucn.py --- a/lib-python/2.7/test/test_ucn.py +++ b/lib-python/2.7/test/test_ucn.py @@ -8,6 +8,8 @@ """#" import unittest +import sys +import _testcapi from test import test_support @@ -137,6 +139,27 @@ unicode, "\\NSPACE", 'unicode-escape', 'strict' ) + @unittest.skipUnless(_testcapi.INT_MAX < _testcapi.PY_SSIZE_T_MAX, + "needs UINT_MAX < SIZE_MAX") + @unittest.skipUnless(_testcapi.UINT_MAX < sys.maxint, + "needs UINT_MAX < sys.maxint") + @test_support.bigmemtest(minsize=_testcapi.UINT_MAX + 1, + memuse=2 + 4 // len(u'\U00010000')) + def test_issue16335(self, size): + func = self.test_issue16335 + if size < func.minsize: + raise unittest.SkipTest("not enough memory: %.1fG minimum needed" % + (func.minsize * func.memuse / float(1024**3),)) + # very very long bogus character name + x = b'\\N{SPACE' + b'x' * int(_testcapi.UINT_MAX + 1) + b'}' + self.assertEqual(len(x), len(b'\\N{SPACE}') + + (_testcapi.UINT_MAX + 1)) + self.assertRaisesRegexp(UnicodeError, + 'unknown Unicode character name', + x.decode, 'unicode-escape' + ) + + def test_main(): test_support.run_unittest(UnicodeNamesTest) diff --git a/lib-python/2.7/test/test_unicode.py b/lib-python/2.7/test/test_unicode.py --- a/lib-python/2.7/test/test_unicode.py +++ b/lib-python/2.7/test/test_unicode.py @@ -644,6 +644,18 @@ return u'\u1234' self.assertEqual('%s' % Wrapper(), u'\u1234') + @test_support.cpython_only + def test_formatting_huge_precision(self): + from _testcapi import INT_MAX + format_string = u"%.{}f".format(INT_MAX + 1) + with self.assertRaises(ValueError): + result = format_string % 2.34 + + def test_formatting_huge_width(self): + format_string = u"%{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format_string % 2.34 + def test_startswith_endswith_errors(self): for meth in (u'foo'.startswith, u'foo'.endswith): with self.assertRaises(UnicodeDecodeError): @@ -1556,6 +1568,21 @@ # will fail self.assertRaises(UnicodeEncodeError, "foo{0}".format, u'\u1000bar') + def test_format_huge_precision(self): + format_string = u".{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format(2.34, format_string) + + def test_format_huge_width(self): + format_string = u"{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format(2.34, format_string) + + def test_format_huge_item_number(self): + format_string = u"{{{}:.6f}}".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format_string.format(2.34) + def test_format_auto_numbering(self): class C: def __init__(self, x=100): diff --git a/lib-python/2.7/test/test_urllib.py b/lib-python/2.7/test/test_urllib.py --- a/lib-python/2.7/test/test_urllib.py +++ b/lib-python/2.7/test/test_urllib.py @@ -222,6 +222,27 @@ finally: self.unfakehttp() + def test_missing_localfile(self): + self.assertRaises(IOError, urllib.urlopen, + 'file://localhost/a/missing/file.py') + fd, tmp_file = tempfile.mkstemp() + tmp_fileurl = 'file://localhost/' + tmp_file.replace(os.path.sep, '/') + try: + self.assertTrue(os.path.exists(tmp_file)) + fp = urllib.urlopen(tmp_fileurl) + finally: + os.close(fd) + fp.close() + os.unlink(tmp_file) + + self.assertFalse(os.path.exists(tmp_file)) + self.assertRaises(IOError, urllib.urlopen, tmp_fileurl) + + def test_ftp_nonexisting(self): + self.assertRaises(IOError, urllib.urlopen, + 'ftp://localhost/not/existing/file.py') + + def test_userpass_inurl(self): self.fakehttp('Hello!') try: diff --git a/lib-python/2.7/test/test_urllib2.py b/lib-python/2.7/test/test_urllib2.py --- a/lib-python/2.7/test/test_urllib2.py +++ b/lib-python/2.7/test/test_urllib2.py @@ -1106,12 +1106,30 @@ self._test_basic_auth(opener, auth_handler, "Authorization", realm, http_handler, password_manager, "http://acme.example.com/protected", - "http://acme.example.com/protected", - ) + "http://acme.example.com/protected" + ) def test_basic_auth_with_single_quoted_realm(self): self.test_basic_auth(quote_char="'") + def test_basic_auth_with_unquoted_realm(self): + opener = OpenerDirector() + password_manager = MockPasswordManager() + auth_handler = urllib2.HTTPBasicAuthHandler(password_manager) + realm = "ACME Widget Store" + http_handler = MockHTTPHandler( + 401, 'WWW-Authenticate: Basic realm=%s\r\n\r\n' % realm) + opener.add_handler(auth_handler) + opener.add_handler(http_handler) + msg = "Basic Auth Realm was unquoted" + with test_support.check_warnings((msg, UserWarning)): + self._test_basic_auth(opener, auth_handler, "Authorization", + realm, http_handler, password_manager, + "http://acme.example.com/protected", + "http://acme.example.com/protected" + ) + + def test_proxy_basic_auth(self): opener = OpenerDirector() ph = urllib2.ProxyHandler(dict(http="proxy.example.com:3128")) @@ -1130,7 +1148,7 @@ ) def test_basic_and_digest_auth_handlers(self): - # HTTPDigestAuthHandler threw an exception if it couldn't handle a 40* + # HTTPDigestAuthHandler raised an exception if it couldn't handle a 40* # response (http://python.org/sf/1479302), where it should instead # return None to allow another handler (especially # HTTPBasicAuthHandler) to handle the response. @@ -1318,16 +1336,32 @@ req = Request(url) self.assertEqual(req.get_full_url(), url) -def test_HTTPError_interface(): - """ - Issue 13211 reveals that HTTPError didn't implement the URLError - interface even though HTTPError is a subclass of URLError. + def test_HTTPError_interface(self): + """ + Issue 13211 reveals that HTTPError didn't implement the URLError + interface even though HTTPError is a subclass of URLError. - >>> err = urllib2.HTTPError(msg='something bad happened', url=None, code=None, hdrs=None, fp=None) - >>> assert hasattr(err, 'reason') - >>> err.reason - 'something bad happened' - """ + >>> err = urllib2.HTTPError(msg='something bad happened', url=None, code=None, hdrs=None, fp=None) + >>> assert hasattr(err, 'reason') + >>> err.reason + 'something bad happened' + """ + + def test_HTTPError_interface_call(self): + """ + Issue 15701= - HTTPError interface has info method available from URLError. + """ + err = urllib2.HTTPError(msg='something bad happened', url=None, + code=None, hdrs='Content-Length:42', fp=None) + self.assertTrue(hasattr(err, 'reason')) + assert hasattr(err, 'reason') + assert hasattr(err, 'info') + assert callable(err.info) + try: + err.info() + except AttributeError: + self.fail("err.info() failed") + self.assertEqual(err.info(), "Content-Length:42") def test_main(verbose=None): from test import test_urllib2 diff --git a/lib-python/2.7/test/test_urllib2_localnet.py b/lib-python/2.7/test/test_urllib2_localnet.py --- a/lib-python/2.7/test/test_urllib2_localnet.py +++ b/lib-python/2.7/test/test_urllib2_localnet.py @@ -5,7 +5,9 @@ import BaseHTTPServer import unittest import hashlib + from test import test_support + mimetools = test_support.import_module('mimetools', deprecated=True) threading = test_support.import_module('threading') @@ -346,6 +348,12 @@ for transparent redirection have been written. """ + def setUp(self): + proxy_handler = urllib2.ProxyHandler({}) + opener = urllib2.build_opener(proxy_handler) + urllib2.install_opener(opener) + super(TestUrlopen, self).setUp() + def start_server(self, responses): handler = GetRequestHandler(responses) diff --git a/lib-python/2.7/test/test_urllib2net.py b/lib-python/2.7/test/test_urllib2net.py --- a/lib-python/2.7/test/test_urllib2net.py +++ b/lib-python/2.7/test/test_urllib2net.py @@ -157,12 +157,12 @@ ## self._test_urls(urls, self._extra_handlers()+[bauth, dauth]) def test_urlwithfrag(self): - urlwith_frag = "http://docs.python.org/glossary.html#glossary" + urlwith_frag = "http://docs.python.org/2/glossary.html#glossary" with test_support.transient_internet(urlwith_frag): req = urllib2.Request(urlwith_frag) res = urllib2.urlopen(req) self.assertEqual(res.geturl(), - "http://docs.python.org/glossary.html#glossary") + "http://docs.python.org/2/glossary.html#glossary") def test_fileno(self): req = urllib2.Request("http://www.python.org") diff --git a/lib-python/2.7/test/test_urlparse.py b/lib-python/2.7/test/test_urlparse.py --- a/lib-python/2.7/test/test_urlparse.py +++ b/lib-python/2.7/test/test_urlparse.py @@ -437,6 +437,51 @@ self.assertEqual(p.port, 80) self.assertEqual(p.geturl(), url) + # Verify an illegal port of value greater than 65535 is set as None + url = "http://www.python.org:65536" + p = urlparse.urlsplit(url) + self.assertEqual(p.port, None) + + def test_issue14072(self): + p1 = urlparse.urlsplit('tel:+31-641044153') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '+31-641044153') + + p2 = urlparse.urlsplit('tel:+31641044153') + self.assertEqual(p2.scheme, 'tel') + self.assertEqual(p2.path, '+31641044153') + + # Assert for urlparse + p1 = urlparse.urlparse('tel:+31-641044153') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '+31-641044153') + + p2 = urlparse.urlparse('tel:+31641044153') + self.assertEqual(p2.scheme, 'tel') + self.assertEqual(p2.path, '+31641044153') + + + def test_telurl_params(self): + p1 = urlparse.urlparse('tel:123-4;phone-context=+1-650-516') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '123-4') + self.assertEqual(p1.params, 'phone-context=+1-650-516') + + p1 = urlparse.urlparse('tel:+1-201-555-0123') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '+1-201-555-0123') + self.assertEqual(p1.params, '') + + p1 = urlparse.urlparse('tel:7042;phone-context=example.com') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '7042') + self.assertEqual(p1.params, 'phone-context=example.com') + + p1 = urlparse.urlparse('tel:863-1234;phone-context=+1-914-555') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '863-1234') + self.assertEqual(p1.params, 'phone-context=+1-914-555') + def test_attributes_bad_port(self): """Check handling of non-integer ports.""" @@ -493,6 +538,10 @@ ('s3','foo.com','/stuff','','','')) self.assertEqual(urlparse.urlparse("x-newscheme://foo.com/stuff"), ('x-newscheme','foo.com','/stuff','','','')) + self.assertEqual(urlparse.urlparse("x-newscheme://foo.com/stuff?query#fragment"), + ('x-newscheme','foo.com','/stuff','','query','fragment')) + self.assertEqual(urlparse.urlparse("x-newscheme://foo.com/stuff?query"), + ('x-newscheme','foo.com','/stuff','','query','')) def test_withoutscheme(self): # Test urlparse without scheme diff --git a/lib-python/2.7/test/test_uu.py b/lib-python/2.7/test/test_uu.py --- a/lib-python/2.7/test/test_uu.py +++ b/lib-python/2.7/test/test_uu.py @@ -48,7 +48,7 @@ out = cStringIO.StringIO() try: uu.decode(inp, out) - self.fail("No exception thrown") + self.fail("No exception raised") except uu.Error, e: self.assertEqual(str(e), "Truncated input file") @@ -57,7 +57,7 @@ out = cStringIO.StringIO() try: uu.decode(inp, out) - self.fail("No exception thrown") + self.fail("No exception raised") except uu.Error, e: self.assertEqual(str(e), "No valid begin line found in input file") diff --git a/lib-python/2.7/test/test_weakref.py b/lib-python/2.7/test/test_weakref.py --- a/lib-python/2.7/test/test_weakref.py +++ b/lib-python/2.7/test/test_weakref.py @@ -33,6 +33,27 @@ return C.method +class Object: + def __init__(self, arg): + self.arg = arg + def __repr__(self): + return "" % self.arg + def __eq__(self, other): + if isinstance(other, Object): + return self.arg == other.arg + return NotImplemented + def __ne__(self, other): + if isinstance(other, Object): + return self.arg != other.arg + return NotImplemented + def __hash__(self): + return hash(self.arg) + +class RefCycle: + def __init__(self): + self.cycle = self + + class TestBase(unittest.TestCase): def setUp(self): @@ -705,6 +726,75 @@ self.assertEqual(b(), None) self.assertEqual(l, [a, b]) + def test_equality(self): + # Alive weakrefs defer equality testing to their underlying object. + x = Object(1) + y = Object(1) + z = Object(2) + a = weakref.ref(x) + b = weakref.ref(y) + c = weakref.ref(z) + d = weakref.ref(x) + # Note how we directly test the operators here, to stress both + # __eq__ and __ne__. + self.assertTrue(a == b) + self.assertFalse(a != b) + self.assertFalse(a == c) + self.assertTrue(a != c) + self.assertTrue(a == d) + self.assertFalse(a != d) + del x, y, z + gc.collect() + for r in a, b, c: + # Sanity check + self.assertIs(r(), None) + # Dead weakrefs compare by identity: whether `a` and `d` are the + # same weakref object is an implementation detail, since they pointed + # to the same original object and didn't have a callback. + # (see issue #16453). + self.assertFalse(a == b) + self.assertTrue(a != b) + self.assertFalse(a == c) + self.assertTrue(a != c) + self.assertEqual(a == d, a is d) + self.assertEqual(a != d, a is not d) + + def test_hashing(self): + # Alive weakrefs hash the same as the underlying object + x = Object(42) + y = Object(42) + a = weakref.ref(x) + b = weakref.ref(y) + self.assertEqual(hash(a), hash(42)) + del x, y + gc.collect() + # Dead weakrefs: + # - retain their hash is they were hashed when alive; + # - otherwise, cannot be hashed. + self.assertEqual(hash(a), hash(42)) + self.assertRaises(TypeError, hash, b) + + def test_trashcan_16602(self): + # Issue #16602: when a weakref's target was part of a long + # deallocation chain, the trashcan mechanism could delay clearing + # of the weakref and make the target object visible from outside + # code even though its refcount had dropped to 0. A crash ensued. + class C(object): + def __init__(self, parent): + if not parent: + return + wself = weakref.ref(self) + def cb(wparent): + o = wself() + self.wparent = weakref.ref(parent, cb) + + d = weakref.WeakKeyDictionary() + root = c = C(None) + for n in range(100): + d[c] = c = C(c) + del root + gc.collect() + class SubclassableWeakrefTestCase(TestBase): @@ -809,17 +899,6 @@ self.assertEqual(self.cbcalled, 0) -class Object: - def __init__(self, arg): - self.arg = arg - def __repr__(self): - return "" % self.arg - -class RefCycle: - def __init__(self): - self.cycle = self - - class MappingTestCase(TestBase): COUNT = 10 diff --git a/lib-python/2.7/test/test_winreg.py b/lib-python/2.7/test/test_winreg.py --- a/lib-python/2.7/test/test_winreg.py +++ b/lib-python/2.7/test/test_winreg.py @@ -1,7 +1,7 @@ # Test the windows specific win32reg module. # Only win32reg functions not hit here: FlushKey, LoadKey and SaveKey -import os, sys +import os, sys, errno import unittest from test import test_support threading = test_support.import_module("threading") @@ -234,7 +234,7 @@ def test_changing_value(self): # Issue2810: A race condition in 2.6 and 3.1 may cause - # EnumValue or QueryValue to throw "WindowsError: More data is + # EnumValue or QueryValue to raise "WindowsError: More data is # available" done = False @@ -267,7 +267,7 @@ def test_long_key(self): # Issue2810, in 2.6 and 3.1 when the key name was exactly 256 - # characters, EnumKey threw "WindowsError: More data is + # characters, EnumKey raised "WindowsError: More data is # available" name = 'x'*256 try: @@ -282,8 +282,14 @@ def test_dynamic_key(self): # Issue2810, when the value is dynamically generated, these - # throw "WindowsError: More data is available" in 2.6 and 3.1 - EnumValue(HKEY_PERFORMANCE_DATA, 0) + # raise "WindowsError: More data is available" in 2.6 and 3.1 + try: + EnumValue(HKEY_PERFORMANCE_DATA, 0) + except OSError as e: + if e.errno in (errno.EPERM, errno.EACCES): + self.skipTest("access denied to registry key " + "(are you running in a non-interactive session?)") + raise QueryValueEx(HKEY_PERFORMANCE_DATA, None) # Reflection requires XP x64/Vista at a minimum. XP doesn't have this stuff @@ -308,6 +314,35 @@ finally: DeleteKey(HKEY_CURRENT_USER, test_key_name) + def test_setvalueex_value_range(self): + # Test for Issue #14420, accept proper ranges for SetValueEx. + # Py2Reg, which gets called by SetValueEx, was using PyLong_AsLong, + # thus raising OverflowError. The implementation now uses + # PyLong_AsUnsignedLong to match DWORD's size. + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + SetValueEx(ck, "test_name", None, REG_DWORD, 0x80000000) + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + def test_queryvalueex_return_value(self): + # Test for Issue #16759, return unsigned int from QueryValueEx. + # Reg2Py, which gets called by QueryValueEx, was returning a value + # generated by PyLong_FromLong. The implmentation now uses + # PyLong_FromUnsignedLong to match DWORD's size. + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + test_val = 0x80000000 + SetValueEx(ck, "test_name", None, REG_DWORD, test_val) + ret_val, ret_type = QueryValueEx(ck, "test_name") + self.assertEqual(ret_type, REG_DWORD) + self.assertEqual(ret_val, test_val) + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + @unittest.skipUnless(REMOTE_NAME, "Skipping remote registry tests") class RemoteWinregTests(BaseWinregTests): diff --git a/lib-python/2.7/test/test_winsound.py b/lib-python/2.7/test/test_winsound.py --- a/lib-python/2.7/test/test_winsound.py +++ b/lib-python/2.7/test/test_winsound.py @@ -2,6 +2,7 @@ import unittest from test import test_support +test_support.requires('audio') import time import os import subprocess diff --git a/lib-python/2.7/test/test_wsgiref.py b/lib-python/2.7/test/test_wsgiref.py --- a/lib-python/2.7/test/test_wsgiref.py +++ b/lib-python/2.7/test/test_wsgiref.py @@ -39,9 +39,6 @@ pass - - - def hello_app(environ,start_response): start_response("200 OK", [ ('Content-Type','text/plain'), @@ -62,27 +59,6 @@ return out.getvalue(), err.getvalue() - - - - - - - - - - - - - - - - - - - - - def compare_generic_iter(make_it,match): """Utility to compare a generic 2.1/2.2+ iterator with an iterable @@ -120,10 +96,6 @@ raise AssertionError("Too many items from .next()",it) - - - - class IntegrationTests(TestCase): def check_hello(self, out, has_length=True): @@ -161,10 +133,6 @@ ) - - - - class UtilityTests(TestCase): def checkShift(self,sn_in,pi_in,part,sn_out,pi_out): @@ -201,11 +169,6 @@ util.setup_testing_defaults(kw) self.assertEqual(util.request_uri(kw,query),uri) - - - - - def checkFW(self,text,size,match): def make_it(text=text,size=size): @@ -224,7 +187,6 @@ it.close() self.assertTrue(it.filelike.closed) - def testSimpleShifts(self): self.checkShift('','/', '', '/', '') self.checkShift('','/x', 'x', '/x', '') @@ -232,7 +194,6 @@ self.checkShift('/a','/x/y', 'x', '/a/x', '/y') self.checkShift('/a','/x/', 'x', '/a/x', '/') - def testNormalizedShifts(self): self.checkShift('/a/b', '/../y', '..', '/a', '/y') self.checkShift('', '/../y', '..', '', '/y') @@ -246,7 +207,6 @@ self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/') self.checkShift('/a/b', '/.', None, '/a/b', '') - def testDefaults(self): for key, value in [ ('SERVER_NAME','127.0.0.1'), @@ -266,7 +226,6 @@ ]: self.checkDefault(key,value) - def testCrossDefaults(self): self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar") self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on") @@ -276,7 +235,6 @@ self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo") self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on") - def testGuessScheme(self): self.assertEqual(util.guess_scheme({}), "http") self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http") @@ -284,10 +242,6 @@ self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https") self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https") - - - - def testAppURIs(self): self.checkAppURI("http://127.0.0.1/") self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam") @@ -411,15 +365,6 @@ raise # for testing, we want to see what's happening - - - - - - - - - class HandlerTests(TestCase): def checkEnvironAttrs(self, handler): @@ -460,7 +405,6 @@ h=TestHandler(); h.setup_environ() self.assertEqual(h.environ['wsgi.url_scheme'],'http') - def testAbstractMethods(self): h = BaseHandler() for name in [ @@ -469,7 +413,6 @@ self.assertRaises(NotImplementedError, getattr(h,name)) self.assertRaises(NotImplementedError, h._write, "test") - def testContentLength(self): # Demo one reason iteration is better than write()... ;) @@ -549,7 +492,6 @@ "\r\n"+MSG) self.assertNotEqual(h.stderr.getvalue().find("AssertionError"), -1) - def testHeaderFormats(self): def non_error_app(e,s): @@ -591,40 +533,28 @@ (stdpat%(version,sw), h.stdout.getvalue()) ) -# This epilogue is needed for compatibility with the Python 2.5 regrtest module + def testCloseOnError(self): + side_effects = {'close_called': False} + MSG = b"Some output has been sent" + def error_app(e,s): + s("200 OK",[])(MSG) + class CrashyIterable(object): + def __iter__(self): + while True: + yield b'blah' + raise AssertionError("This should be caught by handler") + + def close(self): + side_effects['close_called'] = True + return CrashyIterable() + + h = ErrorHandler() + h.run(error_app) + self.assertEqual(side_effects['close_called'], True) + def test_main(): test_support.run_unittest(__name__) if __name__ == "__main__": test_main() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# the above lines intentionally left blank diff --git a/lib-python/2.7/test/test_xml_etree.py b/lib-python/2.7/test/test_xml_etree.py --- a/lib-python/2.7/test/test_xml_etree.py +++ b/lib-python/2.7/test/test_xml_etree.py @@ -1822,6 +1822,26 @@ """ +def check_html_empty_elems_serialization(self): + # issue 15970 + # from http://www.w3.org/TR/html401/index/elements.html + """ + + >>> empty_elems = ['AREA', 'BASE', 'BASEFONT', 'BR', 'COL', 'FRAME', 'HR', + ... 'IMG', 'INPUT', 'ISINDEX', 'LINK', 'META', 'PARAM'] + >>> elems = ''.join('<%s />' % elem for elem in empty_elems) + >>> serialize(ET.XML('%s' % elems), method='html') + '

' + >>> serialize(ET.XML('%s' % elems.lower()), method='html') + '

' + >>> elems = ''.join('<%s>' % (elem, elem) for elem in empty_elems) + >>> serialize(ET.XML('%s' % elems), method='html') + '

' + >>> serialize(ET.XML('%s' % elems.lower()), method='html') + '

' + + """ + # -------------------------------------------------------------------- diff --git a/lib-python/2.7/test/test_xrange.py b/lib-python/2.7/test/test_xrange.py --- a/lib-python/2.7/test/test_xrange.py +++ b/lib-python/2.7/test/test_xrange.py @@ -46,6 +46,28 @@ self.fail('{}: wrong element at position {};' 'expected {}, got {}'.format(test_id, i, y, x)) + def assert_xranges_equivalent(self, x, y): + # Check that two xrange objects are equivalent, in the sense of the + # associated sequences being the same. We want to use this for large + # xrange objects, so instead of converting to lists and comparing + # directly we do a number of indirect checks. + if len(x) != len(y): + self.fail('{} and {} have different ' + 'lengths: {} and {} '.format(x, y, len(x), len(y))) + if len(x) >= 1: + if x[0] != y[0]: + self.fail('{} and {} have different initial ' + 'elements: {} and {} '.format(x, y, x[0], y[0])) + if x[-1] != y[-1]: + self.fail('{} and {} have different final ' + 'elements: {} and {} '.format(x, y, x[-1], y[-1])) + if len(x) >= 2: + x_step = x[1] - x[0] + y_step = y[1] - y[0] + if x_step != y_step: + self.fail('{} and {} have different step: ' + '{} and {} '.format(x, y, x_step, y_step)) + def test_xrange(self): self.assertEqual(list(xrange(3)), [0, 1, 2]) self.assertEqual(list(xrange(1, 5)), [1, 2, 3, 4]) @@ -104,6 +126,59 @@ self.assertEqual(list(pickle.loads(pickle.dumps(r, proto))), list(r)) + M = min(sys.maxint, sys.maxsize) + large_testcases = testcases + [ + (0, M, 1), + (M, 0, -1), + (0, M, M - 1), + (M // 2, M, 1), + (0, -M, -1), + (0, -M, 1 - M), + (-M, M, 2), + (-M, M, 1024), + (-M, M, 10585), + (M, -M, -2), + (M, -M, -1024), + (M, -M, -10585), + ] + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + for t in large_testcases: + r = xrange(*t) + r_out = pickle.loads(pickle.dumps(r, proto)) + self.assert_xranges_equivalent(r_out, r) + + def test_repr(self): + # Check that repr of an xrange is a valid representation + # of that xrange. + + # Valid xranges have at most min(sys.maxint, sys.maxsize) elements. + M = min(sys.maxint, sys.maxsize) + + testcases = [ + (13,), + (0, 11), + (-22, 10), + (20, 3, -1), + (13, 21, 3), + (-2, 2, 2), + (0, M, 1), + (M, 0, -1), + (0, M, M - 1), + (M // 2, M, 1), + (0, -M, -1), + (0, -M, 1 - M), + (-M, M, 2), + (-M, M, 1024), + (-M, M, 10585), + (M, -M, -2), + (M, -M, -1024), + (M, -M, -10585), + ] + for t in testcases: + r = xrange(*t) + r_out = eval(repr(r)) + self.assert_xranges_equivalent(r, r_out) + def test_range_iterators(self): # see issue 7298 limits = [base + jiggle diff --git a/lib-python/2.7/test/test_zipfile.py b/lib-python/2.7/test/test_zipfile.py --- a/lib-python/2.7/test/test_zipfile.py +++ b/lib-python/2.7/test/test_zipfile.py @@ -26,7 +26,7 @@ SMALL_TEST_DATA = [('_ziptest1', '1q2w3e4r5t'), ('ziptest2dir/_ziptest2', 'qawsedrftg'), - ('/ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'), + ('ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'), ('ziptest2dir/ziptest3dir/ziptest4dir/_ziptest3', '6y7u8i9o0p')] @@ -391,10 +391,7 @@ writtenfile = zipfp.extract(fpath) # make sure it was written to the right place - if os.path.isabs(fpath): - correctfile = os.path.join(os.getcwd(), fpath[1:]) - else: - correctfile = os.path.join(os.getcwd(), fpath) + correctfile = os.path.join(os.getcwd(), fpath) correctfile = os.path.normpath(correctfile) self.assertEqual(writtenfile, correctfile) @@ -414,10 +411,7 @@ with zipfile.ZipFile(TESTFN2, "r") as zipfp: zipfp.extractall() for fpath, fdata in SMALL_TEST_DATA: - if os.path.isabs(fpath): - outfile = os.path.join(os.getcwd(), fpath[1:]) - else: - outfile = os.path.join(os.getcwd(), fpath) + outfile = os.path.join(os.getcwd(), fpath) self.assertEqual(fdata, open(outfile, "rb").read()) os.remove(outfile) @@ -425,6 +419,92 @@ # remove the test file subdirectories shutil.rmtree(os.path.join(os.getcwd(), 'ziptest2dir')) + def check_file(self, filename, content): + self.assertTrue(os.path.isfile(filename)) + with open(filename, 'rb') as f: + self.assertEqual(f.read(), content) + + def test_extract_hackers_arcnames(self): + hacknames = [ + ('../foo/bar', 'foo/bar'), + ('foo/../bar', 'foo/bar'), + ('foo/../../bar', 'foo/bar'), + ('foo/bar/..', 'foo/bar'), + ('./../foo/bar', 'foo/bar'), + ('/foo/bar', 'foo/bar'), + ('/foo/../bar', 'foo/bar'), + ('/foo/../../bar', 'foo/bar'), + ] + if os.path.sep == '\\': + hacknames.extend([ + (r'..\foo\bar', 'foo/bar'), + (r'..\/foo\/bar', 'foo/bar'), + (r'foo/\..\/bar', 'foo/bar'), + (r'foo\/../\bar', 'foo/bar'), + (r'C:foo/bar', 'foo/bar'), + (r'C:/foo/bar', 'foo/bar'), + (r'C://foo/bar', 'foo/bar'), + (r'C:\foo\bar', 'foo/bar'), + (r'//conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'), + (r'\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'), + (r'///conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'), + (r'\\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'), + (r'//conky//mountpoint/foo/bar', 'conky/mountpoint/foo/bar'), + (r'\\conky\\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'), + (r'//?/C:/foo/bar', '_/C_/foo/bar'), + (r'\\?\C:\foo\bar', '_/C_/foo/bar'), + (r'C:/../C:/foo/bar', 'C_/foo/bar'), + (r'a:b\ce|f"g?h*i', 'b/c_d_e_f_g_h_i'), + ('../../foo../../ba..r', 'foo/ba..r'), + ]) + else: # Unix + hacknames.extend([ + ('//foo/bar', 'foo/bar'), + ('../../foo../../ba..r', 'foo../ba..r'), + (r'foo/..\bar', r'foo/..\bar'), + ]) + + for arcname, fixedname in hacknames: + content = b'foobar' + arcname.encode() + with zipfile.ZipFile(TESTFN2, 'w', zipfile.ZIP_STORED) as zipfp: + zinfo = zipfile.ZipInfo() + # preserve backslashes + zinfo.filename = arcname + zinfo.external_attr = 0o600 << 16 + zipfp.writestr(zinfo, content) + + arcname = arcname.replace(os.sep, "/") + targetpath = os.path.join('target', 'subdir', 'subsub') + correctfile = os.path.join(targetpath, *fixedname.split('/')) + + with zipfile.ZipFile(TESTFN2, 'r') as zipfp: + writtenfile = zipfp.extract(arcname, targetpath) + self.assertEqual(writtenfile, correctfile, + msg="extract %r" % arcname) + self.check_file(correctfile, content) + shutil.rmtree('target') + + with zipfile.ZipFile(TESTFN2, 'r') as zipfp: + zipfp.extractall(targetpath) + self.check_file(correctfile, content) + shutil.rmtree('target') + + correctfile = os.path.join(os.getcwd(), *fixedname.split('/')) + + with zipfile.ZipFile(TESTFN2, 'r') as zipfp: + writtenfile = zipfp.extract(arcname) + self.assertEqual(writtenfile, correctfile, + msg="extract %r" % arcname) + self.check_file(correctfile, content) + shutil.rmtree(fixedname.split('/')[0]) + + with zipfile.ZipFile(TESTFN2, 'r') as zipfp: + zipfp.extractall() + self.check_file(correctfile, content) + shutil.rmtree(fixedname.split('/')[0]) + + os.remove(TESTFN2) + def test_writestr_compression(self): zipfp = zipfile.ZipFile(TESTFN2, "w") zipfp.writestr("a.txt", "hello world", compress_type=zipfile.ZIP_STORED) @@ -760,6 +840,20 @@ chk = zipfile.is_zipfile(fp) self.assertTrue(not chk) + def test_damaged_zipfile(self): + """Check that zipfiles with missing bytes at the end raise BadZipFile.""" + # - Create a valid zip file + fp = io.BytesIO() + with zipfile.ZipFile(fp, mode="w") as zipf: + zipf.writestr("foo.txt", b"O, for a Muse of Fire!") + zipfiledata = fp.getvalue() + + # - Now create copies of it missing the last N bytes and make sure + # a BadZipFile exception is raised when we try to open it + for N in range(len(zipfiledata)): + fp = io.BytesIO(zipfiledata[:N]) + self.assertRaises(zipfile.BadZipfile, zipfile.ZipFile, fp) + def test_is_zip_valid_file(self): """Check that is_zipfile() correctly identifies zip files.""" # - passing a filename @@ -811,7 +905,7 @@ with zipfile.ZipFile(data, mode="w") as zipf: zipf.writestr("foo.txt", "O, for a Muse of Fire!") - # This is correct; calling .read on a closed ZipFile should throw + # This is correct; calling .read on a closed ZipFile should raise # a RuntimeError, and so should calling .testzip. An earlier # version of .testzip would swallow this exception (and any other) # and report that the first file in the archive was corrupt. @@ -859,6 +953,17 @@ caught.""" self.assertRaises(RuntimeError, zipfile.ZipFile, TESTFN, "w", -1) + def test_unsupported_compression(self): + # data is declared as shrunk, but actually deflated + data = (b'PK\x03\x04.\x00\x00\x00\x01\x00\xe4C\xa1@\x00\x00\x00' + b'\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00x\x03\x00PK\x01' + b'\x02.\x03.\x00\x00\x00\x01\x00\xe4C\xa1@\x00\x00\x00\x00\x02\x00\x00' + b'\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x80\x01\x00\x00\x00\x00xPK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' + b'/\x00\x00\x00!\x00\x00\x00\x00\x00') + with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: + self.assertRaises(NotImplementedError, zipf.open, 'x') + def test_null_byte_in_filename(self): """Check that a filename containing a null byte is properly terminated.""" @@ -908,6 +1013,22 @@ with zipfile.ZipFile(TESTFN, mode="r") as zipf: self.assertEqual(zipf.comment, comment2) + def test_change_comment_in_empty_archive(self): + with zipfile.ZipFile(TESTFN, "a", zipfile.ZIP_STORED) as zipf: + self.assertFalse(zipf.filelist) + zipf.comment = b"this is a comment" + with zipfile.ZipFile(TESTFN, "r") as zipf: + self.assertEqual(zipf.comment, b"this is a comment") + + def test_change_comment_in_nonempty_archive(self): + with zipfile.ZipFile(TESTFN, "w", zipfile.ZIP_STORED) as zipf: + zipf.writestr("foo.txt", "O, for a Muse of Fire!") + with zipfile.ZipFile(TESTFN, "a", zipfile.ZIP_STORED) as zipf: + self.assertTrue(zipf.filelist) + zipf.comment = b"this is a comment" + with zipfile.ZipFile(TESTFN, "r") as zipf: + self.assertEqual(zipf.comment, b"this is a comment") + def check_testzip_with_bad_crc(self, compression): """Tests that files with bad CRCs return their name from testzip.""" zipdata = self.zips_with_bad_crc[compression] diff --git a/lib-python/2.7/test/test_zipimport_support.py b/lib-python/2.7/test/test_zipimport_support.py --- a/lib-python/2.7/test/test_zipimport_support.py +++ b/lib-python/2.7/test/test_zipimport_support.py @@ -29,7 +29,8 @@ # test_cmd_line_script (covers the zipimport support in runpy) # Retrieve some helpers from other test cases -from test import test_doctest, sample_doctest +from test import (test_doctest, sample_doctest, sample_doctest_no_doctests, + sample_doctest_no_docstrings) from test.test_importhooks import ImportHooksBaseTestCase @@ -99,16 +100,26 @@ "test_zipped_doctest") test_src = test_src.replace("test.sample_doctest", "sample_zipped_doctest") - sample_src = inspect.getsource(sample_doctest) - sample_src = sample_src.replace("test.test_doctest", - "test_zipped_doctest") + # The sample doctest files rewritten to include in the zipped version. + sample_sources = {} + for mod in [sample_doctest, sample_doctest_no_doctests, + sample_doctest_no_docstrings]: + src = inspect.getsource(mod) + src = src.replace("test.test_doctest", "test_zipped_doctest") + # Rewrite the module name so that, for example, + # "test.sample_doctest" becomes "sample_zipped_doctest". + mod_name = mod.__name__.split(".")[-1] + mod_name = mod_name.replace("sample_", "sample_zipped_") + sample_sources[mod_name] = src + with temp_dir() as d: script_name = make_script(d, 'test_zipped_doctest', test_src) zip_name, run_name = make_zip_script(d, 'test_zip', script_name) z = zipfile.ZipFile(zip_name, 'a') - z.writestr("sample_zipped_doctest.py", sample_src) + for mod_name, src in sample_sources.items(): + z.writestr(mod_name + ".py", src) z.close() if verbose: zip_file = zipfile.ZipFile(zip_name, 'r') @@ -168,9 +179,10 @@ test_zipped_doctest.test_unittest_reportflags, ] # Needed for test_DocTestParser and test_debug - deprecations = [ + deprecations = [] + if __debug__: # Ignore all warnings about the use of class Tester in this module. - ("class Tester is deprecated", DeprecationWarning)] + deprecations.append(("class Tester is deprecated", DeprecationWarning)) if sys.py3kwarning: deprecations += [ ("backquote not supported", SyntaxWarning), diff --git a/lib-python/2.7/test/test_zlib.py b/lib-python/2.7/test/test_zlib.py --- a/lib-python/2.7/test/test_zlib.py +++ b/lib-python/2.7/test/test_zlib.py @@ -396,6 +396,18 @@ y += dco.flush() self.assertEqual(y, 'foo') + def test_flush_with_freed_input(self): + # Issue #16411: decompressor accesses input to last decompress() call + # in flush(), even if this object has been freed in the meanwhile. + input1 = 'abcdefghijklmnopqrstuvwxyz' + input2 = 'QWERTYUIOPASDFGHJKLZXCVBNM' + data = zlib.compress(input1) + dco = zlib.decompressobj() + dco.decompress(data, 1) + del data + data = zlib.compress(input2) + self.assertEqual(dco.flush(), input1[1:]) + if hasattr(zlib.compressobj(), "copy"): def test_compresscopy(self): # Test copying a compression object @@ -426,6 +438,31 @@ c.flush() self.assertRaises(ValueError, c.copy) + def test_decompress_unused_data(self): + # Repeated calls to decompress() after EOF should accumulate data in + # dco.unused_data, instead of just storing the arg to the last call. + source = b'abcdefghijklmnopqrstuvwxyz' + remainder = b'0123456789' + y = zlib.compress(source) + x = y + remainder + for maxlen in 0, 1000: + for step in 1, 2, len(y), len(x): + dco = zlib.decompressobj() + data = b'' + for i in range(0, len(x), step): + if i < len(y): + self.assertEqual(dco.unused_data, b'') + if maxlen == 0: + data += dco.decompress(x[i : i + step]) + self.assertEqual(dco.unconsumed_tail, b'') + else: + data += dco.decompress( + dco.unconsumed_tail + x[i : i + step], maxlen) + data += dco.flush() + self.assertEqual(data, source) + self.assertEqual(dco.unconsumed_tail, b'') + self.assertEqual(dco.unused_data, remainder) + if hasattr(zlib.decompressobj(), "copy"): def test_decompresscopy(self): # Test copying a decompression object diff --git a/lib-python/2.7/test/testtar.tar b/lib-python/2.7/test/testtar.tar index bac0e2628f35243f236db2fac82737882699b2f0..440182a437da84ef3d61d8cbc0f4209254615b37 GIT binary patch [stripped] diff --git a/lib-python/2.7/textwrap.py b/lib-python/2.7/textwrap.py --- a/lib-python/2.7/textwrap.py +++ b/lib-python/2.7/textwrap.py @@ -9,6 +9,14 @@ import string, re +try: + _unicode = unicode +except NameError: + # If Python is built without Unicode support, the unicode type + # will not exist. Fake one. + class _unicode(object): + pass + # Do the right thing with boolean values for all known Python versions # (so this module can be copied to projects that don't depend on Python # 2.3, e.g. Optik and Docutils) by uncommenting the block of code below. @@ -147,7 +155,7 @@ if self.replace_whitespace: if isinstance(text, str): text = text.translate(self.whitespace_trans) - elif isinstance(text, unicode): + elif isinstance(text, _unicode): text = text.translate(self.unicode_whitespace_trans) return text @@ -167,7 +175,7 @@ 'use', ' ', 'the', ' ', '-b', ' ', option!' otherwise. """ - if isinstance(text, unicode): + if isinstance(text, _unicode): if self.break_on_hyphens: pat = self.wordsep_re_uni else: diff --git a/lib-python/2.7/threading.py b/lib-python/2.7/threading.py --- a/lib-python/2.7/threading.py +++ b/lib-python/2.7/threading.py @@ -10,6 +10,7 @@ import warnings +from collections import deque as _deque from time import time as _time, sleep as _sleep from traceback import format_exc as _format_exc @@ -605,6 +606,10 @@ pass def __stop(self): + # DummyThreads delete self.__block, but they have no waiters to + # notify anyway (join() is forbidden on them). + if not hasattr(self, '_Thread__block'): + return self.__block.acquire() self.__stopped = True self.__block.notify_all() @@ -909,7 +914,7 @@ self.rc = Condition(self.mon) self.wc = Condition(self.mon) self.limit = limit - self.queue = deque() + self.queue = _deque() def put(self, item): self.mon.acquire() diff --git a/lib-python/2.7/tokenize.py b/lib-python/2.7/tokenize.py --- a/lib-python/2.7/tokenize.py +++ b/lib-python/2.7/tokenize.py @@ -70,10 +70,10 @@ Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''" # Tail end of """ string. Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""' -Triple = group("[uU]?[rR]?'''", '[uU]?[rR]?"""') +Triple = group("[uUbB]?[rR]?'''", '[uUbB]?[rR]?"""') # Single-line ' or " string. -String = group(r"[uU]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*'", - r'[uU]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*"') +String = group(r"[uUbB]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*'", + r'[uUbB]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*"') # Because of leftmost-then-longest match semantics, be sure to put the # longest operators first (e.g., if = came before ==, == would get @@ -91,11 +91,11 @@ Token = Ignore + PlainToken # First (or only) line of ' or " string. -ContStr = group(r"[uU]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*" + +ContStr = group(r"[uUbB]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*" + group("'", r'\\\r?\n'), - r'[uU]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*' + + r'[uUbB]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*' + group('"', r'\\\r?\n')) -PseudoExtras = group(r'\\\r?\n', Comment, Triple) +PseudoExtras = group(r'\\\r?\n|\Z', Comment, Triple) PseudoToken = Whitespace + group(PseudoExtras, Number, Funny, ContStr, Name) tokenprog, pseudoprog, single3prog, double3prog = map( @@ -362,6 +362,8 @@ if pseudomatch: # scan for tokens start, end = pseudomatch.span(1) spos, epos, pos = (lnum, start), (lnum, end), end + if start == end: + continue token, initial = line[start:end], line[start] if initial in numchars or \ diff --git a/lib-python/2.7/traceback.py b/lib-python/2.7/traceback.py --- a/lib-python/2.7/traceback.py +++ b/lib-python/2.7/traceback.py @@ -166,7 +166,7 @@ # >>> raise string1, string2 # deprecated # # Clear these out first because issubtype(string1, SyntaxError) - # would throw another exception and mask the original problem. + # would raise another exception and mask the original problem. if (isinstance(etype, BaseException) or isinstance(etype, types.InstanceType) or etype is None or type(etype) is str): diff --git a/lib-python/2.7/unittest/case.py b/lib-python/2.7/unittest/case.py --- a/lib-python/2.7/unittest/case.py +++ b/lib-python/2.7/unittest/case.py @@ -6,6 +6,7 @@ import difflib import pprint import re +import types import warnings from . import result @@ -55,7 +56,7 @@ Unconditionally skip a test. """ def decorator(test_item): - if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): + if not isinstance(test_item, (type, types.ClassType)): @functools.wraps(test_item) def skip_wrapper(*args, **kwargs): raise SkipTest(reason) @@ -201,7 +202,11 @@ self.addTypeEqualityFunc(tuple, 'assertTupleEqual') self.addTypeEqualityFunc(set, 'assertSetEqual') self.addTypeEqualityFunc(frozenset, 'assertSetEqual') - self.addTypeEqualityFunc(unicode, 'assertMultiLineEqual') + try: + self.addTypeEqualityFunc(unicode, 'assertMultiLineEqual') + except NameError: + # No unicode support in this build + pass def addTypeEqualityFunc(self, typeobj, function): """Add a type specific assertEqual style function to compare a type. @@ -442,10 +447,10 @@ def assertRaises(self, excClass, callableObj=None, *args, **kwargs): - """Fail unless an exception of class excClass is thrown + """Fail unless an exception of class excClass is raised by callableObj when invoked with arguments args and keyword arguments kwargs. If a different type of exception is - thrown, it will not be caught, and the test case will be + raised, it will not be caught, and the test case will be deemed to have suffered an error, exactly as for an unexpected exception. @@ -511,7 +516,7 @@ assertion_func(first, second, msg=msg) def assertNotEqual(self, first, second, msg=None): - """Fail if the two objects are equal as determined by the '==' + """Fail if the two objects are equal as determined by the '!=' operator. """ if not first != second: @@ -871,7 +876,7 @@ - [0, 1, 1] and [1, 0, 1] compare equal. - [0, 0, 1] and [0, 1] compare unequal. """ - first_seq, second_seq = list(actual_seq), list(expected_seq) + first_seq, second_seq = list(expected_seq), list(actual_seq) with warnings.catch_warnings(): if sys.py3kwarning: # Silence Py3k warning raised during the sorting diff --git a/lib-python/2.7/unittest/main.py b/lib-python/2.7/unittest/main.py --- a/lib-python/2.7/unittest/main.py +++ b/lib-python/2.7/unittest/main.py @@ -157,7 +157,10 @@ self.test = self.testLoader.loadTestsFromNames(self.testNames, self.module) - def _do_discovery(self, argv, Loader=loader.TestLoader): + def _do_discovery(self, argv, Loader=None): + if Loader is None: + Loader = lambda: self.testLoader + # handle command line args for test discovery self.progName = '%s discover' % self.progName import optparse diff --git a/lib-python/2.7/unittest/runner.py b/lib-python/2.7/unittest/runner.py --- a/lib-python/2.7/unittest/runner.py +++ b/lib-python/2.7/unittest/runner.py @@ -34,7 +34,7 @@ separator2 = '-' * 70 def __init__(self, stream, descriptions, verbosity): - super(TextTestResult, self).__init__() + super(TextTestResult, self).__init__(stream, descriptions, verbosity) self.stream = stream self.showAll = verbosity > 1 self.dots = verbosity == 1 diff --git a/lib-python/2.7/unittest/signals.py b/lib-python/2.7/unittest/signals.py --- a/lib-python/2.7/unittest/signals.py +++ b/lib-python/2.7/unittest/signals.py @@ -9,6 +9,20 @@ class _InterruptHandler(object): def __init__(self, default_handler): self.called = False + self.original_handler = default_handler + if isinstance(default_handler, int): + if default_handler == signal.SIG_DFL: + # Pretend it's signal.default_int_handler instead. + default_handler = signal.default_int_handler + elif default_handler == signal.SIG_IGN: + # Not quite the same thing as SIG_IGN, but the closest we + # can make it: do nothing. + def default_handler(unused_signum, unused_frame): + pass + else: + raise TypeError("expected SIGINT signal handler to be " + "signal.SIG_IGN, signal.SIG_DFL, or a " + "callable object") self.default_handler = default_handler def __call__(self, signum, frame): @@ -54,4 +68,4 @@ global _interrupt_handler if _interrupt_handler is not None: - signal.signal(signal.SIGINT, _interrupt_handler.default_handler) + signal.signal(signal.SIGINT, _interrupt_handler.original_handler) diff --git a/lib-python/2.7/unittest/test/test_break.py b/lib-python/2.7/unittest/test/test_break.py --- a/lib-python/2.7/unittest/test/test_break.py +++ b/lib-python/2.7/unittest/test/test_break.py @@ -15,9 +15,12 @@ @unittest.skipIf(sys.platform == 'freebsd6', "Test kills regrtest on freebsd6 " "if threads have been used") class TestBreak(unittest.TestCase): + int_handler = None def setUp(self): self._default_handler = signal.getsignal(signal.SIGINT) + if self.int_handler is not None: + signal.signal(signal.SIGINT, self.int_handler) def tearDown(self): signal.signal(signal.SIGINT, self._default_handler) @@ -74,6 +77,10 @@ def testSecondInterrupt(self): + # Can't use skipIf decorator because the signal handler may have + # been changed after defining this method. + if signal.getsignal(signal.SIGINT) == signal.SIG_IGN: + self.skipTest("test requires SIGINT to not be ignored") result = unittest.TestResult() unittest.installHandler() unittest.registerResult(result) @@ -123,6 +130,10 @@ def testHandlerReplacedButCalled(self): + # Can't use skipIf decorator because the signal handler may have + # been changed after defining this method. + if signal.getsignal(signal.SIGINT) == signal.SIG_IGN: + self.skipTest("test requires SIGINT to not be ignored") # If our handler has been replaced (is no longer installed) but is # called by the *new* handler, then it isn't safe to delay the # SIGINT and we should immediately delegate to the default handler @@ -250,3 +261,24 @@ test() self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + at unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") + at unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") + at unittest.skipIf(sys.platform == 'freebsd6', "Test kills regrtest on freebsd6 " + "if threads have been used") +class TestBreakDefaultIntHandler(TestBreak): + int_handler = signal.default_int_handler + + at unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") + at unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") + at unittest.skipIf(sys.platform == 'freebsd6', "Test kills regrtest on freebsd6 " + "if threads have been used") +class TestBreakSignalIgnored(TestBreak): + int_handler = signal.SIG_IGN + + at unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") + at unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") + at unittest.skipIf(sys.platform == 'freebsd6', "Test kills regrtest on freebsd6 " + "if threads have been used") +class TestBreakSignalDefault(TestBreak): + int_handler = signal.SIG_DFL diff --git a/lib-python/2.7/unittest/test/test_discovery.py b/lib-python/2.7/unittest/test/test_discovery.py --- a/lib-python/2.7/unittest/test/test_discovery.py +++ b/lib-python/2.7/unittest/test/test_discovery.py @@ -220,12 +220,26 @@ program = object.__new__(unittest.TestProgram) program.usageExit = usageExit + program.testLoader = None with self.assertRaises(Stop): # too many args program._do_discovery(['one', 'two', 'three', 'four']) + def test_command_line_handling_do_discovery_uses_default_loader(self): + program = object.__new__(unittest.TestProgram) + + class Loader(object): + args = [] + def discover(self, start_dir, pattern, top_level_dir): + self.args.append((start_dir, pattern, top_level_dir)) + return 'tests' + + program.testLoader = Loader() + program._do_discovery(['-v']) + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + def test_command_line_handling_do_discovery_calls_loader(self): program = object.__new__(unittest.TestProgram) diff --git a/lib-python/2.7/unittest/test/test_runner.py b/lib-python/2.7/unittest/test/test_runner.py --- a/lib-python/2.7/unittest/test/test_runner.py +++ b/lib-python/2.7/unittest/test/test_runner.py @@ -149,6 +149,19 @@ self.assertEqual(runner.resultclass, unittest.TextTestResult) + def test_multiple_inheritance(self): + class AResult(unittest.TestResult): + def __init__(self, stream, descriptions, verbosity): + super(AResult, self).__init__(stream, descriptions, verbosity) + + class ATextResult(unittest.TextTestResult, AResult): + pass + + # This used to raise an exception due to TextTestResult not passing + # on arguments in its __init__ super call + ATextResult(None, None, None) + + def testBufferAndFailfast(self): class Test(unittest.TestCase): def testFoo(self): diff --git a/lib-python/2.7/unittest/test/test_skipping.py b/lib-python/2.7/unittest/test/test_skipping.py --- a/lib-python/2.7/unittest/test/test_skipping.py +++ b/lib-python/2.7/unittest/test/test_skipping.py @@ -66,6 +66,36 @@ self.assertEqual(result.skipped, [(test, "testing")]) self.assertEqual(record, []) + def test_skip_non_unittest_class_old_style(self): + @unittest.skip("testing") + class Mixin: + def test_1(self): + record.append(1) + class Foo(Mixin, unittest.TestCase): + pass + record = [] + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(record, []) + + def test_skip_non_unittest_class_new_style(self): + @unittest.skip("testing") + class Mixin(object): + def test_1(self): + record.append(1) + class Foo(Mixin, unittest.TestCase): + pass + record = [] + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(record, []) + def test_expected_failure(self): class Foo(unittest.TestCase): @unittest.expectedFailure diff --git a/lib-python/2.7/urllib2.py b/lib-python/2.7/urllib2.py --- a/lib-python/2.7/urllib2.py +++ b/lib-python/2.7/urllib2.py @@ -102,6 +102,7 @@ import time import urlparse import bisect +import warnings try: from cStringIO import StringIO @@ -109,7 +110,7 @@ from StringIO import StringIO from urllib import (unwrap, unquote, splittype, splithost, quote, - addinfourl, splitport, splittag, + addinfourl, splitport, splittag, toBytes, splitattr, ftpwrapper, splituser, splitpasswd, splitvalue) # support for FileHandler, proxies via environment variables @@ -172,6 +173,9 @@ def reason(self): return self.msg + def info(self): + return self.hdrs + # copied from cookielib.py _cut_port_re = re.compile(r":\d+$") def request_host(request): @@ -828,7 +832,7 @@ # allow for double- and single-quoted realm values # (single quotes are a violation of the RFC, but appear in the wild) rx = re.compile('(?:.*,)*[ \t]*([^ \t]+)[ \t]+' - 'realm=(["\'])(.*?)\\2', re.I) + 'realm=(["\']?)([^"\']*)\\2', re.I) # XXX could pre-emptively send auth info already accepted (RFC 2617, # end of section 2, and section 1.2 immediately after "credentials" @@ -861,6 +865,9 @@ mo = AbstractBasicAuthHandler.rx.search(authreq) if mo: scheme, quote, realm = mo.groups() + if quote not in ['"', "'"]: + warnings.warn("Basic Auth Realm was unquoted", + UserWarning, 2) if scheme.lower() == 'basic': response = self.retry_http_basic_auth(host, req, realm) if response and response.code != 401: diff --git a/lib-python/2.7/urlparse.py b/lib-python/2.7/urlparse.py --- a/lib-python/2.7/urlparse.py +++ b/lib-python/2.7/urlparse.py @@ -40,11 +40,14 @@ 'imap', 'wais', 'file', 'mms', 'https', 'shttp', 'snews', 'prospero', 'rtsp', 'rtspu', 'rsync', '', 'svn', 'svn+ssh', 'sftp','nfs','git', 'git+ssh'] +uses_params = ['ftp', 'hdl', 'prospero', 'http', 'imap', + 'https', 'shttp', 'rtsp', 'rtspu', 'sip', 'sips', + 'mms', '', 'sftp', 'tel'] + +# These are not actually used anymore, but should stay for backwards +# compatibility. (They are undocumented, but have a public-looking name.) non_hierarchical = ['gopher', 'hdl', 'mailto', 'news', 'telnet', 'wais', 'imap', 'snews', 'sip', 'sips'] -uses_params = ['ftp', 'hdl', 'prospero', 'http', 'imap', - 'https', 'shttp', 'rtsp', 'rtspu', 'sip', 'sips', - 'mms', '', 'sftp'] uses_query = ['http', 'wais', 'imap', 'https', 'shttp', 'mms', 'gopher', 'rtsp', 'rtspu', 'sip', 'sips', ''] uses_fragment = ['ftp', 'hdl', 'http', 'gopher', 'news', @@ -104,9 +107,11 @@ netloc = self.netloc.split('@')[-1].split(']')[-1] if ':' in netloc: port = netloc.split(':')[1] - return int(port, 10) - else: - return None + port = int(port, 10) + # verify legal port + if (0 <= port <= 65535): + return port + return None from collections import namedtuple @@ -192,21 +197,21 @@ if c not in scheme_chars: break else: - try: - # make sure "url" is not actually a port number (in which case - # "scheme" is really part of the path - _testportnum = int(url[i+1:]) - except ValueError: - scheme, url = url[:i].lower(), url[i+1:] + # make sure "url" is not actually a port number (in which case + # "scheme" is really part of the path) + rest = url[i+1:] + if not rest or any(c not in '0123456789' for c in rest): + # not a port number + scheme, url = url[:i].lower(), rest if url[:2] == '//': netloc, url = _splitnetloc(url, 2) if (('[' in netloc and ']' not in netloc) or (']' in netloc and '[' not in netloc)): raise ValueError("Invalid IPv6 URL") - if allow_fragments and scheme in uses_fragment and '#' in url: + if allow_fragments and '#' in url: url, fragment = url.split('#', 1) - if scheme in uses_query and '?' in url: + if '?' in url: url, query = url.split('?', 1) v = SplitResult(scheme, netloc, url, query, fragment) _parse_cache[key] = v diff --git a/lib-python/2.7/wave.py b/lib-python/2.7/wave.py --- a/lib-python/2.7/wave.py +++ b/lib-python/2.7/wave.py @@ -261,9 +261,9 @@ # def _read_fmt_chunk(self, chunk): - wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack(' prefix dicts self._current_context = self._ns_contexts[-1] self._undeclared_ns_maps = [] self._encoding = encoding - def _write(self, text): - if isinstance(text, str): - self._out.write(text) - else: - self._out.write(text.encode(self._encoding, _error_handling)) - def _qname(self, name): """Builds a qualified name from a (ns_url, localname) pair""" if name[0]: @@ -120,9 +138,12 @@ # ContentHandler methods def startDocument(self): - self._write('\n' % + self._write(u'\n' % self._encoding) + def endDocument(self): + self._flush() + def startPrefixMapping(self, prefix, uri): self._ns_contexts.append(self._current_context.copy()) self._current_context[uri] = prefix @@ -133,39 +154,39 @@ del self._ns_contexts[-1] def startElement(self, name, attrs): - self._write('<' + name) + self._write(u'<' + name) for (name, value) in attrs.items(): - self._write(' %s=%s' % (name, quoteattr(value))) - self._write('>') + self._write(u' %s=%s' % (name, quoteattr(value))) + self._write(u'>') def endElement(self, name): - self._write('' % name) + self._write(u'' % name) def startElementNS(self, name, qname, attrs): - self._write('<' + self._qname(name)) + self._write(u'<' + self._qname(name)) for prefix, uri in self._undeclared_ns_maps: if prefix: - self._out.write(' xmlns:%s="%s"' % (prefix, uri)) + self._write(u' xmlns:%s="%s"' % (prefix, uri)) else: - self._out.write(' xmlns="%s"' % uri) + self._write(u' xmlns="%s"' % uri) self._undeclared_ns_maps = [] for (name, value) in attrs.items(): - self._write(' %s=%s' % (self._qname(name), quoteattr(value))) - self._write('>') + self._write(u' %s=%s' % (self._qname(name), quoteattr(value))) + self._write(u'>') def endElementNS(self, name, qname): - self._write('' % self._qname(name)) + self._write(u'' % self._qname(name)) def characters(self, content): - self._write(escape(content)) + self._write(escape(unicode(content))) def ignorableWhitespace(self, content): - self._write(content) + self._write(unicode(content)) def processingInstruction(self, target, data): - self._write('' % (target, data)) + self._write(u'' % (target, data)) class XMLFilterBase(xmlreader.XMLReader): @@ -293,14 +314,31 @@ source.setSystemId(f.name) if source.getByteStream() is None: - sysid = source.getSystemId() - basehead = os.path.dirname(os.path.normpath(base)) - sysidfilename = os.path.join(basehead, sysid) - if os.path.isfile(sysidfilename): + try: + sysid = source.getSystemId() + basehead = os.path.dirname(os.path.normpath(base)) + encoding = sys.getfilesystemencoding() + if isinstance(sysid, unicode): + if not isinstance(basehead, unicode): + try: + basehead = basehead.decode(encoding) + except UnicodeDecodeError: + sysid = sysid.encode(encoding) + else: + if isinstance(basehead, unicode): + try: + sysid = sysid.decode(encoding) + except UnicodeDecodeError: + basehead = basehead.encode(encoding) + sysidfilename = os.path.join(basehead, sysid) + isfile = os.path.isfile(sysidfilename) + except UnicodeError: + isfile = False + if isfile: source.setSystemId(sysidfilename) f = open(sysidfilename, "rb") else: - source.setSystemId(urlparse.urljoin(base, sysid)) + source.setSystemId(urlparse.urljoin(base, source.getSystemId())) f = urllib.urlopen(source.getSystemId()) source.setByteStream(f) diff --git a/lib-python/2.7/xml/sax/xmlreader.py b/lib-python/2.7/xml/sax/xmlreader.py --- a/lib-python/2.7/xml/sax/xmlreader.py +++ b/lib-python/2.7/xml/sax/xmlreader.py @@ -68,7 +68,7 @@ SAX parsers are not required to provide localization for errors and warnings; if they cannot support the requested locale, - however, they must throw a SAX exception. Applications may + however, they must raise a SAX exception. Applications may request a locale change in the middle of a parse.""" raise SAXNotSupportedException("Locale support not implemented") diff --git a/lib-python/2.7/xmlrpclib.py b/lib-python/2.7/xmlrpclib.py --- a/lib-python/2.7/xmlrpclib.py +++ b/lib-python/2.7/xmlrpclib.py @@ -945,7 +945,7 @@ class MultiCallIterator: """Iterates over the results of a multicall. Exceptions are - thrown in response to xmlrpc faults.""" + raised in response to xmlrpc faults.""" def __init__(self, results): self.results = results diff --git a/lib-python/2.7/zipfile.py b/lib-python/2.7/zipfile.py --- a/lib-python/2.7/zipfile.py +++ b/lib-python/2.7/zipfile.py @@ -5,6 +5,7 @@ import binascii, cStringIO, stat import io import re +import string try: import zlib # We may need its compression method @@ -166,6 +167,8 @@ return endrec data = fpin.read(sizeEndCentDir64Locator) + if len(data) != sizeEndCentDir64Locator: + return endrec sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data) if sig != stringEndArchive64Locator: return endrec @@ -176,6 +179,8 @@ # Assume no 'zip64 extensible data' fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2) data = fpin.read(sizeEndCentDir64) + if len(data) != sizeEndCentDir64: + return endrec sig, sz, create_version, read_version, disk_num, disk_dir, \ dircount, dircount2, dirsize, diroffset = \ struct.unpack(structEndArchive64, data) @@ -211,7 +216,9 @@ except IOError: return None data = fpin.read() - if data[0:4] == stringEndArchive and data[-2:] == "\000\000": + if (len(data) == sizeEndCentDir and + data[0:4] == stringEndArchive and + data[-2:] == b"\000\000"): # the signature is correct and there's no comment, unpack structure endrec = struct.unpack(structEndArchive, data) endrec=list(endrec) @@ -235,6 +242,9 @@ if start >= 0: # found the magic number; attempt to unpack and interpret recData = data[start:start+sizeEndCentDir] + if len(recData) != sizeEndCentDir: + # Zip file is corrupted. + return None endrec = list(struct.unpack(structEndArchive, recData)) commentSize = endrec[_ECD_COMMENT_SIZE] #as claimed by the zip file comment = data[start+sizeEndCentDir:start+sizeEndCentDir+commentSize] @@ -246,7 +256,7 @@ endrec) # Unable to find a valid end of central directory structure - return + return None class ZipInfo (object): @@ -316,7 +326,7 @@ # compress_size Size of the compressed file # file_size Size of the uncompressed file - def FileHeader(self): + def FileHeader(self, zip64=None): """Return the per-file header as a string.""" dt = self.date_time dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] @@ -331,12 +341,17 @@ extra = self.extra - if file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT: - # File is larger than what fits into a 4 byte integer, - # fall back to the ZIP64 extension + if zip64 is None: + zip64 = file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT + if zip64: fmt = ' ZIP64_LIMIT or compress_size > ZIP64_LIMIT: + if not zip64: + raise LargeZipFile("Filesize would require ZIP64 extensions") + # File is larger than what fits into a 4 byte integer, + # fall back to the ZIP64 extension file_size = 0xffffffff compress_size = 0xffffffff self.extract_version = max(45, self.extract_version) @@ -461,6 +476,28 @@ self._UpdateKeys(c) return c + +compressor_names = { + 0: 'store', + 1: 'shrink', + 2: 'reduce', + 3: 'reduce', + 4: 'reduce', + 5: 'reduce', + 6: 'implode', + 7: 'tokenize', + 8: 'deflate', + 9: 'deflate64', + 10: 'implode', + 12: 'bzip2', + 14: 'lzma', + 18: 'terse', + 19: 'lz77', + 97: 'wavpack', + 98: 'ppmd', +} + + class ZipExtFile(io.BufferedIOBase): """File-like object for reading an archive member. Is returned by ZipFile.open(). @@ -475,9 +512,11 @@ # Search for universal newlines or line chunks. PATTERN = re.compile(r'^(?P[^\r\n]+)|(?P\n|\r\n?)') - def __init__(self, fileobj, mode, zipinfo, decrypter=None): + def __init__(self, fileobj, mode, zipinfo, decrypter=None, + close_fileobj=False): self._fileobj = fileobj self._decrypter = decrypter + self._close_fileobj = close_fileobj self._compress_type = zipinfo.compress_type self._compress_size = zipinfo.compress_size @@ -485,6 +524,12 @@ if self._compress_type == ZIP_DEFLATED: self._decompressor = zlib.decompressobj(-15) + elif self._compress_type != ZIP_STORED: + descr = compressor_names.get(self._compress_type) + if descr: + raise NotImplementedError("compression type %d (%s)" % (self._compress_type, descr)) + else: + raise NotImplementedError("compression type %d" % (self._compress_type,)) self._unconsumed = '' self._readbuffer = '' @@ -649,9 +694,15 @@ self._offset += len(data) return data + def close(self): + try : + if self._close_fileobj: + self._fileobj.close() + finally: + super(ZipExtFile, self).close() -class ZipFile: +class ZipFile(object): """ Class with methods to open, read, write, close, list zip files. z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=False) @@ -690,7 +741,7 @@ self.compression = compression # Method of compression self.mode = key = mode.replace('b', '')[0] self.pwd = None - self.comment = '' + self._comment = '' # Check if we were passed a file-like object if isinstance(file, basestring): @@ -710,30 +761,34 @@ self.fp = file self.filename = getattr(file, 'name', None) - if key == 'r': - self._GetContents() - elif key == 'w': - # set the modified flag so central directory gets written - # even if no files are added to the archive - self._didModify = True - elif key == 'a': - try: - # See if file is a zip file + try: + if key == 'r': self._RealGetContents() - # seek to start of directory and overwrite - self.fp.seek(self.start_dir, 0) - except BadZipfile: - # file is not a zip file, just append - self.fp.seek(0, 2) - + elif key == 'w': # set the modified flag so central directory gets written # even if no files are added to the archive self._didModify = True - else: + elif key == 'a': + try: + # See if file is a zip file + self._RealGetContents() + # seek to start of directory and overwrite + self.fp.seek(self.start_dir, 0) + except BadZipfile: + # file is not a zip file, just append + self.fp.seek(0, 2) + + # set the modified flag so central directory gets written + # even if no files are added to the archive + self._didModify = True + else: + raise RuntimeError('Mode must be "r", "w" or "a"') + except: + fp = self.fp + self.fp = None if not self._filePassed: - self.fp.close() - self.fp = None - raise RuntimeError, 'Mode must be "r", "w" or "a"' + fp.close() + raise def __enter__(self): return self @@ -741,17 +796,6 @@ def __exit__(self, type, value, traceback): self.close() - def _GetContents(self): - """Read the directory, making sure we close the file if the format - is bad.""" - try: - self._RealGetContents() - except BadZipfile: - if not self._filePassed: - self.fp.close() - self.fp = None - raise - def _RealGetContents(self): """Read in the table of contents for the ZIP file.""" fp = self.fp @@ -765,7 +809,7 @@ print endrec size_cd = endrec[_ECD_SIZE] # bytes in central directory offset_cd = endrec[_ECD_OFFSET] # offset of central directory - self.comment = endrec[_ECD_COMMENT] # archive comment + self._comment = endrec[_ECD_COMMENT] # archive comment # "concat" is zero, unless zip was concatenated to another file concat = endrec[_ECD_LOCATION] - size_cd - offset_cd @@ -784,9 +828,11 @@ total = 0 while total < size_cd: centdir = fp.read(sizeCentralDir) - if centdir[0:4] != stringCentralDir: - raise BadZipfile, "Bad magic number for central directory" + if len(centdir) != sizeCentralDir: + raise BadZipfile("Truncated central directory") centdir = struct.unpack(structCentralDir, centdir) + if centdir[_CD_SIGNATURE] != stringCentralDir: + raise BadZipfile("Bad magic number for central directory") if self.debug > 2: print centdir filename = fp.read(centdir[_CD_FILENAME_LENGTH]) @@ -845,9 +891,9 @@ try: # Read by chunks, to avoid an OverflowError or a # MemoryError with very large embedded files. - f = self.open(zinfo.filename, "r") - while f.read(chunk_size): # Check CRC-32 - pass + with self.open(zinfo.filename, "r") as f: + while f.read(chunk_size): # Check CRC-32 + pass except BadZipfile: return zinfo.filename @@ -864,6 +910,22 @@ """Set default password for encrypted files.""" self.pwd = pwd + @property + def comment(self): + """The comment text associated with the ZIP file.""" + return self._comment + + @comment.setter + def comment(self, comment): + # check for valid comment length + if len(comment) >= ZIP_MAX_COMMENT: + if self.debug: + print('Archive comment is too long; truncating to %d bytes' + % ZIP_MAX_COMMENT) + comment = comment[:ZIP_MAX_COMMENT] + self._comment = comment + self._didModify = True + def read(self, name, pwd=None): """Return file bytes (as a string) for name.""" return self.open(name, "r", pwd).read() @@ -880,62 +942,72 @@ # given a file object in the constructor if self._filePassed: zef_file = self.fp + should_close = False else: zef_file = open(self.filename, 'rb') + should_close = True - # Make sure we have an info object - if isinstance(name, ZipInfo): - # 'name' is already an info object - zinfo = name - else: - # Get info object for name - zinfo = self.getinfo(name) + try: + # Make sure we have an info object + if isinstance(name, ZipInfo): + # 'name' is already an info object + zinfo = name + else: + # Get info object for name + zinfo = self.getinfo(name) - zef_file.seek(zinfo.header_offset, 0) + zef_file.seek(zinfo.header_offset, 0) - # Skip the file header: - fheader = zef_file.read(sizeFileHeader) - if fheader[0:4] != stringFileHeader: - raise BadZipfile, "Bad magic number for file header" + # Skip the file header: + fheader = zef_file.read(sizeFileHeader) + if len(fheader) != sizeFileHeader: + raise BadZipfile("Truncated file header") + fheader = struct.unpack(structFileHeader, fheader) + if fheader[_FH_SIGNATURE] != stringFileHeader: + raise BadZipfile("Bad magic number for file header") - fheader = struct.unpack(structFileHeader, fheader) - fname = zef_file.read(fheader[_FH_FILENAME_LENGTH]) - if fheader[_FH_EXTRA_FIELD_LENGTH]: - zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH]) + fname = zef_file.read(fheader[_FH_FILENAME_LENGTH]) + if fheader[_FH_EXTRA_FIELD_LENGTH]: + zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH]) - if fname != zinfo.orig_filename: - raise BadZipfile, \ - 'File name in directory "%s" and header "%s" differ.' % ( - zinfo.orig_filename, fname) + if fname != zinfo.orig_filename: + raise BadZipfile, \ + 'File name in directory "%s" and header "%s" differ.' % ( + zinfo.orig_filename, fname) - # check for encrypted flag & handle password - is_encrypted = zinfo.flag_bits & 0x1 - zd = None - if is_encrypted: - if not pwd: - pwd = self.pwd - if not pwd: - raise RuntimeError, "File %s is encrypted, " \ - "password required for extraction" % name + # check for encrypted flag & handle password + is_encrypted = zinfo.flag_bits & 0x1 + zd = None + if is_encrypted: + if not pwd: + pwd = self.pwd + if not pwd: + raise RuntimeError, "File %s is encrypted, " \ + "password required for extraction" % name - zd = _ZipDecrypter(pwd) - # The first 12 bytes in the cypher stream is an encryption header - # used to strengthen the algorithm. The first 11 bytes are - # completely random, while the 12th contains the MSB of the CRC, - # or the MSB of the file time depending on the header type - # and is used to check the correctness of the password. - bytes = zef_file.read(12) - h = map(zd, bytes[0:12]) - if zinfo.flag_bits & 0x8: - # compare against the file type from extended local headers - check_byte = (zinfo._raw_time >> 8) & 0xff - else: - # compare against the CRC otherwise - check_byte = (zinfo.CRC >> 24) & 0xff - if ord(h[11]) != check_byte: - raise RuntimeError("Bad password for file", name) + zd = _ZipDecrypter(pwd) + # The first 12 bytes in the cypher stream is an encryption header + # used to strengthen the algorithm. The first 11 bytes are + # completely random, while the 12th contains the MSB of the CRC, + # or the MSB of the file time depending on the header type + # and is used to check the correctness of the password. + bytes = zef_file.read(12) + h = map(zd, bytes[0:12]) + if zinfo.flag_bits & 0x8: + # compare against the file type from extended local headers + check_byte = (zinfo._raw_time >> 8) & 0xff + else: + # compare against the CRC otherwise + check_byte = (zinfo.CRC >> 24) & 0xff + if ord(h[11]) != check_byte: + raise RuntimeError("Bad password for file", name) - return ZipExtFile(zef_file, mode, zinfo, zd) + return ZipExtFile(zef_file, mode, zinfo, zd, + close_fileobj=should_close) + except: + if should_close: + zef_file.close() + raise def extract(self, member, path=None, pwd=None): """Extract a member from the archive to the current working directory, @@ -969,17 +1041,25 @@ """ # build the destination pathname, replacing # forward slashes to platform specific separators. - # Strip trailing path separator, unless it represents the root. - if (targetpath[-1:] in (os.path.sep, os.path.altsep) - and len(os.path.splitdrive(targetpath)[1]) > 1): - targetpath = targetpath[:-1] + arcname = member.filename.replace('/', os.path.sep) - # don't include leading "/" from file name if present - if member.filename[0] == '/': - targetpath = os.path.join(targetpath, member.filename[1:]) - else: - targetpath = os.path.join(targetpath, member.filename) + if os.path.altsep: + arcname = arcname.replace(os.path.altsep, os.path.sep) + # interpret absolute pathname as relative, remove drive letter or + # UNC path, redundant separators, "." and ".." components. + arcname = os.path.splitdrive(arcname)[1] + arcname = os.path.sep.join(x for x in arcname.split(os.path.sep) + if x not in ('', os.path.curdir, os.path.pardir)) + if os.path.sep == '\\': + # filter illegal characters on Windows + illegal = ':<>|"?*' + table = string.maketrans(illegal, '_' * len(illegal)) + arcname = arcname.translate(table) + # remove trailing dots + arcname = (x.rstrip('.') for x in arcname.split(os.path.sep)) + arcname = os.path.sep.join(x for x in arcname if x) + targetpath = os.path.join(targetpath, arcname) targetpath = os.path.normpath(targetpath) # Create all upper directories if necessary. @@ -992,11 +1072,9 @@ os.mkdir(targetpath) return targetpath - source = self.open(member, pwd=pwd) - target = file(targetpath, "wb") - shutil.copyfileobj(source, target) - source.close() - target.close() + with self.open(member, pwd=pwd) as source, \ + file(targetpath, "wb") as target: + shutil.copyfileobj(source, target) return targetpath @@ -1062,20 +1140,23 @@ zinfo.CRC = 0 self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo - self.fp.write(zinfo.FileHeader()) + self.fp.write(zinfo.FileHeader(False)) return with open(filename, "rb") as fp: # Must overwrite CRC and sizes with correct data later zinfo.CRC = CRC = 0 zinfo.compress_size = compress_size = 0 - zinfo.file_size = file_size = 0 - self.fp.write(zinfo.FileHeader()) + # Compressed size can be larger than uncompressed size + zip64 = self._allowZip64 and \ + zinfo.file_size * 1.05 > ZIP64_LIMIT + self.fp.write(zinfo.FileHeader(zip64)) if zinfo.compress_type == ZIP_DEFLATED: cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) else: cmpr = None + file_size = 0 while 1: buf = fp.read(1024 * 8) if not buf: @@ -1095,11 +1176,16 @@ zinfo.compress_size = file_size zinfo.CRC = CRC zinfo.file_size = file_size - # Seek backwards and write CRC and file sizes + if not zip64 and self._allowZip64: + if file_size > ZIP64_LIMIT: + raise RuntimeError('File size has increased during compressing') + if compress_size > ZIP64_LIMIT: + raise RuntimeError('Compressed size larger than uncompressed size') + # Seek backwards and write file header (which will now include + # correct CRC and file sizes) position = self.fp.tell() # Preserve current position in file - self.fp.seek(zinfo.header_offset + 14, 0) - self.fp.write(struct.pack(" ZIP64_LIMIT or \ + zinfo.compress_size > ZIP64_LIMIT + if zip64 and not self._allowZip64: + raise LargeZipFile("Filesize would require ZIP64 extensions") + self.fp.write(zinfo.FileHeader(zip64)) self.fp.write(bytes) - self.fp.flush() if zinfo.flag_bits & 0x08: # Write CRC and file sizes after the file data - self.fp.write(struct.pack(" ZIP64_LIMIT \ - or zinfo.compress_size > ZIP64_LIMIT: - extra.append(zinfo.file_size) - extra.append(zinfo.compress_size) - file_size = 0xffffffff - compress_size = 0xffffffff - else: - file_size = zinfo.file_size - compress_size = zinfo.compress_size + try: + if self.mode in ("w", "a") and self._didModify: # write ending records + count = 0 + pos1 = self.fp.tell() + for zinfo in self.filelist: # write central directory + count = count + 1 + dt = zinfo.date_time + dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] + dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) + extra = [] + if zinfo.file_size > ZIP64_LIMIT \ + or zinfo.compress_size > ZIP64_LIMIT: + extra.append(zinfo.file_size) + extra.append(zinfo.compress_size) + file_size = 0xffffffff + compress_size = 0xffffffff + else: + file_size = zinfo.file_size + compress_size = zinfo.compress_size - if zinfo.header_offset > ZIP64_LIMIT: - extra.append(zinfo.header_offset) - header_offset = 0xffffffffL - else: - header_offset = zinfo.header_offset + if zinfo.header_offset > ZIP64_LIMIT: + extra.append(zinfo.header_offset) + header_offset = 0xffffffffL + else: + header_offset = zinfo.header_offset - extra_data = zinfo.extra - if extra: - # Append a ZIP64 field to the extra's - extra_data = struct.pack( - '>sys.stderr, (structCentralDir, - stringCentralDir, create_version, - zinfo.create_system, extract_version, zinfo.reserved, - zinfo.flag_bits, zinfo.compress_type, dostime, dosdate, - zinfo.CRC, compress_size, file_size, - len(zinfo.filename), len(extra_data), len(zinfo.comment), - 0, zinfo.internal_attr, zinfo.external_attr, - header_offset) - raise - self.fp.write(centdir) - self.fp.write(filename) - self.fp.write(extra_data) - self.fp.write(zinfo.comment) + try: + filename, flag_bits = zinfo._encodeFilenameFlags() + centdir = struct.pack(structCentralDir, + stringCentralDir, create_version, + zinfo.create_system, extract_version, zinfo.reserved, + flag_bits, zinfo.compress_type, dostime, dosdate, + zinfo.CRC, compress_size, file_size, + len(filename), len(extra_data), len(zinfo.comment), + 0, zinfo.internal_attr, zinfo.external_attr, + header_offset) + except DeprecationWarning: + print >>sys.stderr, (structCentralDir, + stringCentralDir, create_version, + zinfo.create_system, extract_version, zinfo.reserved, + zinfo.flag_bits, zinfo.compress_type, dostime, dosdate, + zinfo.CRC, compress_size, file_size, + len(zinfo.filename), len(extra_data), len(zinfo.comment), + 0, zinfo.internal_attr, zinfo.external_attr, + header_offset) + raise + self.fp.write(centdir) + self.fp.write(filename) + self.fp.write(extra_data) + self.fp.write(zinfo.comment) - pos2 = self.fp.tell() - # Write end-of-zip-archive record - centDirCount = count - centDirSize = pos2 - pos1 - centDirOffset = pos1 - if (centDirCount >= ZIP_FILECOUNT_LIMIT or - centDirOffset > ZIP64_LIMIT or - centDirSize > ZIP64_LIMIT): - # Need to write the ZIP64 end-of-archive records - zip64endrec = struct.pack( - structEndArchive64, stringEndArchive64, - 44, 45, 45, 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset) - self.fp.write(zip64endrec) + pos2 = self.fp.tell() + # Write end-of-zip-archive record + centDirCount = count + centDirSize = pos2 - pos1 + centDirOffset = pos1 + if (centDirCount >= ZIP_FILECOUNT_LIMIT or + centDirOffset > ZIP64_LIMIT or + centDirSize > ZIP64_LIMIT): + # Need to write the ZIP64 end-of-archive records + zip64endrec = struct.pack( + structEndArchive64, stringEndArchive64, + 44, 45, 45, 0, 0, centDirCount, centDirCount, + centDirSize, centDirOffset) + self.fp.write(zip64endrec) - zip64locrec = struct.pack( - structEndArchive64Locator, - stringEndArchive64Locator, 0, pos2, 1) - self.fp.write(zip64locrec) - centDirCount = min(centDirCount, 0xFFFF) - centDirSize = min(centDirSize, 0xFFFFFFFF) - centDirOffset = min(centDirOffset, 0xFFFFFFFF) + zip64locrec = struct.pack( + structEndArchive64Locator, + stringEndArchive64Locator, 0, pos2, 1) + self.fp.write(zip64locrec) + centDirCount = min(centDirCount, 0xFFFF) + centDirSize = min(centDirSize, 0xFFFFFFFF) + centDirOffset = min(centDirOffset, 0xFFFFFFFF) - # check for valid comment length - if len(self.comment) >= ZIP_MAX_COMMENT: - if self.debug > 0: - msg = 'Archive comment is too long; truncating to %d bytes' \ - % ZIP_MAX_COMMENT - self.comment = self.comment[:ZIP_MAX_COMMENT] - - endrec = struct.pack(structEndArchive, stringEndArchive, - 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset, len(self.comment)) - self.fp.write(endrec) - self.fp.write(self.comment) - self.fp.flush() - - if not self._filePassed: - self.fp.close() - self.fp = None + endrec = struct.pack(structEndArchive, stringEndArchive, + 0, 0, centDirCount, centDirCount, + centDirSize, centDirOffset, len(self._comment)) + self.fp.write(endrec) + self.fp.write(self._comment) + self.fp.flush() + finally: + fp = self.fp + self.fp = None + if not self._filePassed: + fp.close() class PyZipFile(ZipFile): @@ -1381,16 +1466,15 @@ if len(args) != 2: print USAGE sys.exit(1) - zf = ZipFile(args[1], 'r') - zf.printdir() - zf.close() + with ZipFile(args[1], 'r') as zf: + zf.printdir() elif args[0] == '-t': if len(args) != 2: print USAGE sys.exit(1) - zf = ZipFile(args[1], 'r') - badfile = zf.testzip() + with ZipFile(args[1], 'r') as zf: + badfile = zf.testzip() if badfile: print("The following enclosed file is corrupted: {!r}".format(badfile)) print "Done testing" @@ -1400,20 +1484,19 @@ print USAGE sys.exit(1) - zf = ZipFile(args[1], 'r') - out = args[2] - for path in zf.namelist(): - if path.startswith('./'): - tgt = os.path.join(out, path[2:]) - else: - tgt = os.path.join(out, path) + with ZipFile(args[1], 'r') as zf: + out = args[2] + for path in zf.namelist(): + if path.startswith('./'): + tgt = os.path.join(out, path[2:]) + else: + tgt = os.path.join(out, path) - tgtdir = os.path.dirname(tgt) - if not os.path.exists(tgtdir): - os.makedirs(tgtdir) - with open(tgt, 'wb') as fp: - fp.write(zf.read(path)) - zf.close() + tgtdir = os.path.dirname(tgt) + if not os.path.exists(tgtdir): + os.makedirs(tgtdir) + with open(tgt, 'wb') as fp: + fp.write(zf.read(path)) elif args[0] == '-c': if len(args) < 3: @@ -1429,11 +1512,9 @@ os.path.join(path, nm), os.path.join(zippath, nm)) # else: ignore - zf = ZipFile(args[1], 'w', allowZip64=True) - for src in args[2:]: - addToZip(zf, src, os.path.basename(src)) - - zf.close() + with ZipFile(args[1], 'w', allowZip64=True) as zf: + for src in args[2:]: + addToZip(zf, src, os.path.basename(src)) if __name__ == "__main__": main() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:43 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:43 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Add_MAXREPEAT_to_=5Fsre=2E?= Message-ID: <3ZPmPW1xl4zR08@mail.python.org> http://hg.python.org/jython/rev/c2a08a8868cc changeset: 7075:c2a08a8868cc user: Frank Wierzbicki date: Fri Mar 08 16:18:44 2013 -0800 summary: Add MAXREPEAT to _sre. files: src/org/python/modules/_sre.java | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/src/org/python/modules/_sre.java b/src/org/python/modules/_sre.java --- a/src/org/python/modules/_sre.java +++ b/src/org/python/modules/_sre.java @@ -22,6 +22,9 @@ public class _sre { public static int MAGIC = SRE_STATE.SRE_MAGIC; + // probably the right number for Jython since we are UTF-16. + public static int MAXREPEAT = 65535; + // workaround the fact that H, I types are unsigned, but we are not really using them as such // XXX: May not be the right size, but I suspect it is -- see sre_compile.py public static int CODESIZE = 4; -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:44 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:44 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_our_=5Fcsv=2E_See_ht?= =?utf-8?q?tp=3A//bugs=2Epython=2Eorg/issue16013?= Message-ID: <3ZPmPX4NbFzQyq@mail.python.org> http://hg.python.org/jython/rev/019685a1fde6 changeset: 7076:019685a1fde6 user: Frank Wierzbicki date: Sat Mar 09 13:01:55 2013 -0800 summary: Update our _csv. See http://bugs.python.org/issue16013 files: src/org/python/modules/_csv/PyReader.java | 12 +++++++--- 1 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/org/python/modules/_csv/PyReader.java b/src/org/python/modules/_csv/PyReader.java --- a/src/org/python/modules/_csv/PyReader.java +++ b/src/org/python/modules/_csv/PyReader.java @@ -69,11 +69,15 @@ lineobj = input_iter.__iternext__(); if (lineobj == null) { // End of input OR exception - if (field.length() != 0) { - throw _csv.Error("newline inside string"); - } else { - return null; + if (field.length() != 0 || state == ParserState.IN_QUOTED_FIELD) { + if (dialect.strict) { + throw _csv.Error("unexpected end of data"); + } else { + parse_save_field(); + break; + } } + return null; } line_num++; -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:46 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:46 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Merge_in_changes_from_lates?= =?utf-8?q?t_doctest=2Epy?= Message-ID: <3ZPmPZ0JkgzRM8@mail.python.org> http://hg.python.org/jython/rev/41371e4cf9f4 changeset: 7077:41371e4cf9f4 user: Frank Wierzbicki date: Sun Mar 10 10:16:30 2013 -0700 summary: Merge in changes from latest doctest.py files: Lib/doctest.py | 17 +++++++++++++---- 1 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Lib/doctest.py b/Lib/doctest.py --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -2318,7 +2318,8 @@ return "Doctest: " + self._dt_test.name class SkipDocTestCase(DocTestCase): - def __init__(self): + def __init__(self, module): + self.module = module DocTestCase.__init__(self, None) def setUp(self): @@ -2328,7 +2329,10 @@ pass def shortDescription(self): - return "Skipping tests from %s" % module.__name__ + return "Skipping tests from %s" % self.module.__name__ + + __str__ = shortDescription + def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, **options): @@ -2376,12 +2380,17 @@ if not tests and sys.flags.optimize >=2: # Skip doctests when running with -O2 suite = unittest.TestSuite() - suite.addTest(SkipDocTestCase()) + suite.addTest(SkipDocTestCase(module)) return suite elif not tests: # Why do we want to do this? Because it reveals a bug that might # otherwise be hidden. - raise ValueError(module, "has no tests") + # It is probably a bug that this exception is not also raised if the + # number of doctest examples in tests is zero (i.e. if no doctest + # examples were found). However, we should probably not be raising + # an exception at all here, though it is too late to make this change + # for a maintenance release. See also issue #14649. + raise ValueError(module, "has no docstrings") tests.sort() suite = unittest.TestSuite() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:47 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:47 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Patch_decimal=2Epy_with_lat?= =?utf-8?q?est_from_2=2E7=2E?= Message-ID: <3ZPmPb4HkMzR08@mail.python.org> http://hg.python.org/jython/rev/76bddc8d8265 changeset: 7078:76bddc8d8265 user: Frank Wierzbicki date: Sun Mar 10 17:01:34 2013 -0700 summary: Patch decimal.py with latest from 2.7. files: Lib/decimal.py | 8 +++++++- Lib/test/test_decimal.py | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletions(-) diff --git a/Lib/decimal.py b/Lib/decimal.py --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -1580,7 +1580,13 @@ def __float__(self): """Float representation.""" - return float(str(self)) + if self._isnan(): + if self.is_snan(): + raise ValueError("Cannot convert signaling NaN to float") + s = "-nan" if self._sign else "nan" + else: + s = str(self) + return float(s) def __int__(self): """Converts self to an int, truncating if necessary.""" diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1449,6 +1449,18 @@ self.assertEqual(float(d1), 66) self.assertEqual(float(d2), 15.32) + def test_nan_to_float(self): + # Test conversions of decimal NANs to float. + # See http://bugs.python.org/issue15544 + for s in ('nan', 'nan1234', '-nan', '-nan2468'): + f = float(Decimal(s)) + self.assertTrue(math.isnan(f)) + + def test_snan_to_float(self): + for s in ('snan', '-snan', 'snan1357', '-snan1234'): + d = Decimal(s) + self.assertRaises(ValueError, float, d) + def test_eval_round_trip(self): #with zero -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:49 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:49 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Forgot_to_add_files_from_2?= =?utf-8?q?=2E7_rev_82552=3Aa8047d1376d5?= Message-ID: <3ZPmPd6N7qzQwB@mail.python.org> http://hg.python.org/jython/rev/ed8002b646f3 changeset: 7079:ed8002b646f3 user: Frank Wierzbicki date: Sun Mar 10 19:22:20 2013 -0700 summary: Forgot to add files from 2.7 rev 82552:a8047d1376d5 files: lib-python/2.7/_osx_support.py | 488 ++++++++++ lib-python/2.7/json/tests/test_tool.py | 69 + lib-python/2.7/test/mp_fork_bomb.py | 16 + lib-python/2.7/test/sample_doctest_no_docstrings.py | 12 + lib-python/2.7/test/sample_doctest_no_doctests.py | 15 + lib-python/2.7/test/test__osx_support.py | 279 +++++ lib-python/2.7/test/test_file_eintr.py | 239 ++++ lib-python/2.7/test/testbz2_bigmem.bz2 | Bin 8 files changed, 1118 insertions(+), 0 deletions(-) diff --git a/lib-python/2.7/_osx_support.py b/lib-python/2.7/_osx_support.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/_osx_support.py @@ -0,0 +1,488 @@ +"""Shared OS X support functions.""" + +import os +import re +import sys + +__all__ = [ + 'compiler_fixup', + 'customize_config_vars', + 'customize_compiler', + 'get_platform_osx', +] + +# configuration variables that may contain universal build flags, +# like "-arch" or "-isdkroot", that may need customization for +# the user environment +_UNIVERSAL_CONFIG_VARS = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', 'BASECFLAGS', + 'BLDSHARED', 'LDSHARED', 'CC', 'CXX', + 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS', + 'PY_CORE_CFLAGS') + +# configuration variables that may contain compiler calls +_COMPILER_CONFIG_VARS = ('BLDSHARED', 'LDSHARED', 'CC', 'CXX') + +# prefix added to original configuration variable names +_INITPRE = '_OSX_SUPPORT_INITIAL_' + + +def _find_executable(executable, path=None): + """Tries to find 'executable' in the directories listed in 'path'. + + A string listing directories separated by 'os.pathsep'; defaults to + os.environ['PATH']. Returns the complete filename or None if not found. + """ + if path is None: + path = os.environ['PATH'] + + paths = path.split(os.pathsep) + base, ext = os.path.splitext(executable) + + if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'): + executable = executable + '.exe' + + if not os.path.isfile(executable): + for p in paths: + f = os.path.join(p, executable) + if os.path.isfile(f): + # the file exists, we have a shot at spawn working + return f + return None + else: + return executable + + +def _read_output(commandstring): + """Output from succesful command execution or None""" + # Similar to os.popen(commandstring, "r").read(), + # but without actually using os.popen because that + # function is not usable during python bootstrap. + # tempfile is also not available then. + import contextlib + try: + import tempfile + fp = tempfile.NamedTemporaryFile() + except ImportError: + fp = open("/tmp/_osx_support.%s"%( + os.getpid(),), "w+b") + + with contextlib.closing(fp) as fp: + cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name) + return fp.read().decode('utf-8').strip() if not os.system(cmd) else None + + +def _find_build_tool(toolname): + """Find a build tool on current path or using xcrun""" + return (_find_executable(toolname) + or _read_output("/usr/bin/xcrun -find %s" % (toolname,)) + or '' + ) + +_SYSTEM_VERSION = None + +def _get_system_version(): + """Return the OS X system version as a string""" + # Reading this plist is a documented way to get the system + # version (see the documentation for the Gestalt Manager) + # We avoid using platform.mac_ver to avoid possible bootstrap issues during + # the build of Python itself (distutils is used to build standard library + # extensions). + + global _SYSTEM_VERSION + + if _SYSTEM_VERSION is None: + _SYSTEM_VERSION = '' + try: + f = open('/System/Library/CoreServices/SystemVersion.plist') + except IOError: + # We're on a plain darwin box, fall back to the default + # behaviour. + pass + else: + try: + m = re.search(r'ProductUserVisibleVersion\s*' + r'(.*?)', f.read()) + finally: + f.close() + if m is not None: + _SYSTEM_VERSION = '.'.join(m.group(1).split('.')[:2]) + # else: fall back to the default behaviour + + return _SYSTEM_VERSION + +def _remove_original_values(_config_vars): + """Remove original unmodified values for testing""" + # This is needed for higher-level cross-platform tests of get_platform. + for k in list(_config_vars): + if k.startswith(_INITPRE): + del _config_vars[k] + +def _save_modified_value(_config_vars, cv, newvalue): + """Save modified and original unmodified value of configuration var""" + + oldvalue = _config_vars.get(cv, '') + if (oldvalue != newvalue) and (_INITPRE + cv not in _config_vars): + _config_vars[_INITPRE + cv] = oldvalue + _config_vars[cv] = newvalue + +def _supports_universal_builds(): + """Returns True if universal builds are supported on this system""" + # As an approximation, we assume that if we are running on 10.4 or above, + # then we are running with an Xcode environment that supports universal + # builds, in particular -isysroot and -arch arguments to the compiler. This + # is in support of allowing 10.4 universal builds to run on 10.3.x systems. + + osx_version = _get_system_version() + if osx_version: + try: + osx_version = tuple(int(i) for i in osx_version.split('.')) + except ValueError: + osx_version = '' + return bool(osx_version >= (10, 4)) if osx_version else False + + +def _find_appropriate_compiler(_config_vars): + """Find appropriate C compiler for extension module builds""" + + # Issue #13590: + # The OSX location for the compiler varies between OSX + # (or rather Xcode) releases. With older releases (up-to 10.5) + # the compiler is in /usr/bin, with newer releases the compiler + # can only be found inside Xcode.app if the "Command Line Tools" + # are not installed. + # + # Futhermore, the compiler that can be used varies between + # Xcode releases. Upto Xcode 4 it was possible to use 'gcc-4.2' + # as the compiler, after that 'clang' should be used because + # gcc-4.2 is either not present, or a copy of 'llvm-gcc' that + # miscompiles Python. + + # skip checks if the compiler was overriden with a CC env variable + if 'CC' in os.environ: + return _config_vars + + # The CC config var might contain additional arguments. + # Ignore them while searching. + cc = oldcc = _config_vars['CC'].split()[0] + if not _find_executable(cc): + # Compiler is not found on the shell search PATH. + # Now search for clang, first on PATH (if the Command LIne + # Tools have been installed in / or if the user has provided + # another location via CC). If not found, try using xcrun + # to find an uninstalled clang (within a selected Xcode). + + # NOTE: Cannot use subprocess here because of bootstrap + # issues when building Python itself (and os.popen is + # implemented on top of subprocess and is therefore not + # usable as well) + + cc = _find_build_tool('clang') + + elif os.path.basename(cc).startswith('gcc'): + # Compiler is GCC, check if it is LLVM-GCC + data = _read_output("'%s' --version" + % (cc.replace("'", "'\"'\"'"),)) + if 'llvm-gcc' in data: + # Found LLVM-GCC, fall back to clang + cc = _find_build_tool('clang') + + if not cc: + raise SystemError( + "Cannot locate working compiler") + + if cc != oldcc: + # Found a replacement compiler. + # Modify config vars using new compiler, if not already explictly + # overriden by an env variable, preserving additional arguments. + for cv in _COMPILER_CONFIG_VARS: + if cv in _config_vars and cv not in os.environ: + cv_split = _config_vars[cv].split() + cv_split[0] = cc if cv != 'CXX' else cc + '++' + _save_modified_value(_config_vars, cv, ' '.join(cv_split)) + + return _config_vars + + +def _remove_universal_flags(_config_vars): + """Remove all universal build arguments from config vars""" + + for cv in _UNIVERSAL_CONFIG_VARS: + # Do not alter a config var explicitly overriden by env var + if cv in _config_vars and cv not in os.environ: + flags = _config_vars[cv] + flags = re.sub('-arch\s+\w+\s', ' ', flags) + flags = re.sub('-isysroot [^ \t]*', ' ', flags) + _save_modified_value(_config_vars, cv, flags) + + return _config_vars + + +def _remove_unsupported_archs(_config_vars): + """Remove any unsupported archs from config vars""" + # Different Xcode releases support different sets for '-arch' + # flags. In particular, Xcode 4.x no longer supports the + # PPC architectures. + # + # This code automatically removes '-arch ppc' and '-arch ppc64' + # when these are not supported. That makes it possible to + # build extensions on OSX 10.7 and later with the prebuilt + # 32-bit installer on the python.org website. + + # skip checks if the compiler was overriden with a CC env variable + if 'CC' in os.environ: + return _config_vars + + if re.search('-arch\s+ppc', _config_vars['CFLAGS']) is not None: + # NOTE: Cannot use subprocess here because of bootstrap + # issues when building Python itself + status = os.system("'%s' -arch ppc -x c /dev/null 2>/dev/null"%( + _config_vars['CC'].replace("'", "'\"'\"'"),)) + # The Apple compiler drivers return status 255 if no PPC + if (status >> 8) == 255: + # Compiler doesn't support PPC, remove the related + # '-arch' flags if not explicitly overridden by an + # environment variable + for cv in _UNIVERSAL_CONFIG_VARS: + if cv in _config_vars and cv not in os.environ: + flags = _config_vars[cv] + flags = re.sub('-arch\s+ppc\w*\s', ' ', flags) + _save_modified_value(_config_vars, cv, flags) + + return _config_vars + + +def _override_all_archs(_config_vars): + """Allow override of all archs with ARCHFLAGS env var""" + # NOTE: This name was introduced by Apple in OSX 10.5 and + # is used by several scripting languages distributed with + # that OS release. + if 'ARCHFLAGS' in os.environ: + arch = os.environ['ARCHFLAGS'] + for cv in _UNIVERSAL_CONFIG_VARS: + if cv in _config_vars and '-arch' in _config_vars[cv]: + flags = _config_vars[cv] + flags = re.sub('-arch\s+\w+\s', ' ', flags) + flags = flags + ' ' + arch + _save_modified_value(_config_vars, cv, flags) + + return _config_vars + + +def _check_for_unavailable_sdk(_config_vars): + """Remove references to any SDKs not available""" + # If we're on OSX 10.5 or later and the user tries to + # compile an extension using an SDK that is not present + # on the current machine it is better to not use an SDK + # than to fail. This is particularly important with + # the standalong Command Line Tools alternative to a + # full-blown Xcode install since the CLT packages do not + # provide SDKs. If the SDK is not present, it is assumed + # that the header files and dev libs have been installed + # to /usr and /System/Library by either a standalone CLT + # package or the CLT component within Xcode. + cflags = _config_vars.get('CFLAGS', '') + m = re.search(r'-isysroot\s+(\S+)', cflags) + if m is not None: + sdk = m.group(1) + if not os.path.exists(sdk): + for cv in _UNIVERSAL_CONFIG_VARS: + # Do not alter a config var explicitly overriden by env var + if cv in _config_vars and cv not in os.environ: + flags = _config_vars[cv] + flags = re.sub(r'-isysroot\s+\S+(?:\s|$)', ' ', flags) + _save_modified_value(_config_vars, cv, flags) + + return _config_vars + + +def compiler_fixup(compiler_so, cc_args): + """ + This function will strip '-isysroot PATH' and '-arch ARCH' from the + compile flags if the user has specified one them in extra_compile_flags. + + This is needed because '-arch ARCH' adds another architecture to the + build, without a way to remove an architecture. Furthermore GCC will + barf if multiple '-isysroot' arguments are present. + """ + stripArch = stripSysroot = False + + compiler_so = list(compiler_so) + + if not _supports_universal_builds(): + # OSX before 10.4.0, these don't support -arch and -isysroot at + # all. + stripArch = stripSysroot = True + else: + stripArch = '-arch' in cc_args + stripSysroot = '-isysroot' in cc_args + + if stripArch or 'ARCHFLAGS' in os.environ: + while True: + try: + index = compiler_so.index('-arch') + # Strip this argument and the next one: + del compiler_so[index:index+2] + except ValueError: + break + + if 'ARCHFLAGS' in os.environ and not stripArch: + # User specified different -arch flags in the environ, + # see also distutils.sysconfig + compiler_so = compiler_so + os.environ['ARCHFLAGS'].split() + + if stripSysroot: + while True: + try: + index = compiler_so.index('-isysroot') + # Strip this argument and the next one: + del compiler_so[index:index+2] + except ValueError: + break + + # Check if the SDK that is used during compilation actually exists, + # the universal build requires the usage of a universal SDK and not all + # users have that installed by default. + sysroot = None + if '-isysroot' in cc_args: + idx = cc_args.index('-isysroot') + sysroot = cc_args[idx+1] + elif '-isysroot' in compiler_so: + idx = compiler_so.index('-isysroot') + sysroot = compiler_so[idx+1] + + if sysroot and not os.path.isdir(sysroot): + from distutils import log + log.warn("Compiling with an SDK that doesn't seem to exist: %s", + sysroot) + log.warn("Please check your Xcode installation") + + return compiler_so + + +def customize_config_vars(_config_vars): + """Customize Python build configuration variables. + + Called internally from sysconfig with a mutable mapping + containing name/value pairs parsed from the configured + makefile used to build this interpreter. Returns + the mapping updated as needed to reflect the environment + in which the interpreter is running; in the case of + a Python from a binary installer, the installed + environment may be very different from the build + environment, i.e. different OS levels, different + built tools, different available CPU architectures. + + This customization is performed whenever + distutils.sysconfig.get_config_vars() is first + called. It may be used in environments where no + compilers are present, i.e. when installing pure + Python dists. Customization of compiler paths + and detection of unavailable archs is deferred + until the first extention module build is + requested (in distutils.sysconfig.customize_compiler). + + Currently called from distutils.sysconfig + """ + + if not _supports_universal_builds(): + # On Mac OS X before 10.4, check if -arch and -isysroot + # are in CFLAGS or LDFLAGS and remove them if they are. + # This is needed when building extensions on a 10.3 system + # using a universal build of python. + _remove_universal_flags(_config_vars) + + # Allow user to override all archs with ARCHFLAGS env var + _override_all_archs(_config_vars) + + # Remove references to sdks that are not found + _check_for_unavailable_sdk(_config_vars) + + return _config_vars + + +def customize_compiler(_config_vars): + """Customize compiler path and configuration variables. + + This customization is performed when the first + extension module build is requested + in distutils.sysconfig.customize_compiler). + """ + + # Find a compiler to use for extension module builds + _find_appropriate_compiler(_config_vars) + + # Remove ppc arch flags if not supported here + _remove_unsupported_archs(_config_vars) + + # Allow user to override all archs with ARCHFLAGS env var + _override_all_archs(_config_vars) + + return _config_vars + + +def get_platform_osx(_config_vars, osname, release, machine): + """Filter values for get_platform()""" + # called from get_platform() in sysconfig and distutils.util + # + # For our purposes, we'll assume that the system version from + # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set + # to. This makes the compatibility story a bit more sane because the + # machine is going to compile and link as if it were + # MACOSX_DEPLOYMENT_TARGET. + + macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '') + macrelease = _get_system_version() or macver + macver = macver or macrelease + + if macver: + release = macver + osname = "macosx" + + # Use the original CFLAGS value, if available, so that we + # return the same machine type for the platform string. + # Otherwise, distutils may consider this a cross-compiling + # case and disallow installs. + cflags = _config_vars.get(_INITPRE+'CFLAGS', + _config_vars.get('CFLAGS', '')) + if ((macrelease + '.') >= '10.4.' and + '-arch' in cflags.strip()): + # The universal build will build fat binaries, but not on + # systems before 10.4 + + machine = 'fat' + + archs = re.findall('-arch\s+(\S+)', cflags) + archs = tuple(sorted(set(archs))) + + if len(archs) == 1: + machine = archs[0] + elif archs == ('i386', 'ppc'): + machine = 'fat' + elif archs == ('i386', 'x86_64'): + machine = 'intel' + elif archs == ('i386', 'ppc', 'x86_64'): + machine = 'fat3' + elif archs == ('ppc64', 'x86_64'): + machine = 'fat64' + elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'): + machine = 'universal' + else: + raise ValueError( + "Don't know machine value for archs=%r" % (archs,)) + + elif machine == 'i386': + # On OSX the machine type returned by uname is always the + # 32-bit variant, even if the executable architecture is + # the 64-bit variant + if sys.maxint >= 2**32: + machine = 'x86_64' + + elif machine in ('PowerPC', 'Power_Macintosh'): + # Pick a sane name for the PPC architecture. + # See 'i386' case + if sys.maxint >= 2**32: + machine = 'ppc64' + else: + machine = 'ppc' + + return (osname, release, machine) diff --git a/lib-python/2.7/json/tests/test_tool.py b/lib-python/2.7/json/tests/test_tool.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/json/tests/test_tool.py @@ -0,0 +1,69 @@ +import os +import sys +import textwrap +import unittest +import subprocess +from test import test_support +from test.script_helper import assert_python_ok + +class TestTool(unittest.TestCase): + data = """ + + [["blorpie"],[ "whoops" ] , [ + ],\t"d-shtaeou",\r"d-nthiouh", + "i-vhbjkhnth", {"nifty":87}, {"morefield" :\tfalse,"field" + :"yes"} ] + """ + + expect = textwrap.dedent("""\ + [ + [ + "blorpie" + ], + [ + "whoops" + ], + [], + "d-shtaeou", + "d-nthiouh", + "i-vhbjkhnth", + { + "nifty": 87 + }, + { + "field": "yes", + "morefield": false + } + ] + """) + + def test_stdin_stdout(self): + proc = subprocess.Popen( + (sys.executable, '-m', 'json.tool'), + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + out, err = proc.communicate(self.data.encode()) + self.assertEqual(out.splitlines(), self.expect.encode().splitlines()) + self.assertEqual(err, None) + + def _create_infile(self): + infile = test_support.TESTFN + with open(infile, "w") as fp: + self.addCleanup(os.remove, infile) + fp.write(self.data) + return infile + + def test_infile_stdout(self): + infile = self._create_infile() + rc, out, err = assert_python_ok('-m', 'json.tool', infile) + self.assertEqual(out.splitlines(), self.expect.encode().splitlines()) + self.assertEqual(err, b'') + + def test_infile_outfile(self): + infile = self._create_infile() + outfile = test_support.TESTFN + '.out' + rc, out, err = assert_python_ok('-m', 'json.tool', infile, outfile) + self.addCleanup(os.remove, outfile) + with open(outfile, "r") as fp: + self.assertEqual(fp.read(), self.expect) + self.assertEqual(out, b'') + self.assertEqual(err, b'') diff --git a/lib-python/2.7/test/mp_fork_bomb.py b/lib-python/2.7/test/mp_fork_bomb.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/mp_fork_bomb.py @@ -0,0 +1,16 @@ +import multiprocessing + +def foo(conn): + conn.send("123") + +# Because "if __name__ == '__main__'" is missing this will not work +# correctly on Windows. However, we should get a RuntimeError rather +# than the Windows equivalent of a fork bomb. + +r, w = multiprocessing.Pipe(False) +p = multiprocessing.Process(target=foo, args=(w,)) +p.start() +w.close() +print(r.recv()) +r.close() +p.join() diff --git a/lib-python/2.7/test/sample_doctest_no_docstrings.py b/lib-python/2.7/test/sample_doctest_no_docstrings.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/sample_doctest_no_docstrings.py @@ -0,0 +1,12 @@ +# This is a sample module used for testing doctest. +# +# This module is for testing how doctest handles a module with no +# docstrings. + + +class Foo(object): + + # A class with no docstring. + + def __init__(self): + pass diff --git a/lib-python/2.7/test/sample_doctest_no_doctests.py b/lib-python/2.7/test/sample_doctest_no_doctests.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/sample_doctest_no_doctests.py @@ -0,0 +1,15 @@ +"""This is a sample module used for testing doctest. + +This module is for testing how doctest handles a module with docstrings +but no doctest examples. + +""" + + +class Foo(object): + """A docstring with no doctest examples. + + """ + + def __init__(self): + pass diff --git a/lib-python/2.7/test/test__osx_support.py b/lib-python/2.7/test/test__osx_support.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/test__osx_support.py @@ -0,0 +1,279 @@ +""" +Test suite for _osx_support: shared OS X support functions. +""" + +import os +import platform +import shutil +import stat +import sys +import unittest + +import test.test_support + +import _osx_support + + at unittest.skipUnless(sys.platform.startswith("darwin"), "requires OS X") +class Test_OSXSupport(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + self.prog_name = 'bogus_program_xxxx' + self.temp_path_dir = os.path.abspath(os.getcwd()) + self.env = test.test_support.EnvironmentVarGuard() + self.addCleanup(self.env.__exit__) + for cv in ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', + 'BASECFLAGS', 'BLDSHARED', 'LDSHARED', 'CC', + 'CXX', 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS', + 'PY_CORE_CFLAGS'): + if cv in self.env: + self.env.unset(cv) + + def add_expected_saved_initial_values(self, config_vars, expected_vars): + # Ensure that the initial values for all modified config vars + # are also saved with modified keys. + expected_vars.update(('_OSX_SUPPORT_INITIAL_'+ k, + config_vars[k]) for k in config_vars + if config_vars[k] != expected_vars[k]) + + def test__find_executable(self): + if self.env['PATH']: + self.env['PATH'] = self.env['PATH'] + ':' + self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir) + test.test_support.unlink(self.prog_name) + self.assertIsNone(_osx_support._find_executable(self.prog_name)) + self.addCleanup(test.test_support.unlink, self.prog_name) + with open(self.prog_name, 'w') as f: + f.write("#!/bin/sh\n/bin/echo OK\n") + os.chmod(self.prog_name, stat.S_IRWXU) + self.assertEqual(self.prog_name, + _osx_support._find_executable(self.prog_name)) + + def test__read_output(self): + if self.env['PATH']: + self.env['PATH'] = self.env['PATH'] + ':' + self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir) + test.test_support.unlink(self.prog_name) + self.addCleanup(test.test_support.unlink, self.prog_name) + with open(self.prog_name, 'w') as f: + f.write("#!/bin/sh\n/bin/echo ExpectedOutput\n") + os.chmod(self.prog_name, stat.S_IRWXU) + self.assertEqual('ExpectedOutput', + _osx_support._read_output(self.prog_name)) + + def test__find_build_tool(self): + out = _osx_support._find_build_tool('cc') + self.assertTrue(os.path.isfile(out), + 'cc not found - check xcode-select') + + def test__get_system_version(self): + self.assertTrue(platform.mac_ver()[0].startswith( + _osx_support._get_system_version())) + + def test__remove_original_values(self): + config_vars = { + 'CC': 'gcc-test -pthreads', + } + expected_vars = { + 'CC': 'clang -pthreads', + } + cv = 'CC' + newvalue = 'clang -pthreads' + _osx_support._save_modified_value(config_vars, cv, newvalue) + self.assertNotEqual(expected_vars, config_vars) + _osx_support._remove_original_values(config_vars) + self.assertEqual(expected_vars, config_vars) + + def test__save_modified_value(self): + config_vars = { + 'CC': 'gcc-test -pthreads', + } + expected_vars = { + 'CC': 'clang -pthreads', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + cv = 'CC' + newvalue = 'clang -pthreads' + _osx_support._save_modified_value(config_vars, cv, newvalue) + self.assertEqual(expected_vars, config_vars) + + def test__save_modified_value_unchanged(self): + config_vars = { + 'CC': 'gcc-test -pthreads', + } + expected_vars = config_vars.copy() + cv = 'CC' + newvalue = 'gcc-test -pthreads' + _osx_support._save_modified_value(config_vars, cv, newvalue) + self.assertEqual(expected_vars, config_vars) + + def test__supports_universal_builds(self): + import platform + self.assertEqual(platform.mac_ver()[0].split('.') >= ['10', '4'], + _osx_support._supports_universal_builds()) + + def test__find_appropriate_compiler(self): + compilers = ( + ('gcc-test', 'i686-apple-darwin11-llvm-gcc-4.2'), + ('clang', 'clang version 3.1'), + ) + config_vars = { + 'CC': 'gcc-test -pthreads', + 'CXX': 'cc++-test', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-test -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-test -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + expected_vars = { + 'CC': 'clang -pthreads', + 'CXX': 'clang++', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'clang -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'clang -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + suffix = (':' + self.env['PATH']) if self.env['PATH'] else '' + self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix + for c_name, c_output in compilers: + test.test_support.unlink(c_name) + self.addCleanup(test.test_support.unlink, c_name) + with open(c_name, 'w') as f: + f.write("#!/bin/sh\n/bin/echo " + c_output) + os.chmod(c_name, stat.S_IRWXU) + self.assertEqual(expected_vars, + _osx_support._find_appropriate_compiler( + config_vars)) + + def test__remove_universal_flags(self): + config_vars = { + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + expected_vars = { + 'CFLAGS': '-fno-strict-aliasing -g -O3 ', + 'LDFLAGS': ' -g', + 'CPPFLAGS': '-I. ', + 'BLDSHARED': 'gcc-4.0 -bundle -g', + 'LDSHARED': 'gcc-4.0 -bundle -g', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + self.assertEqual(expected_vars, + _osx_support._remove_universal_flags( + config_vars)) + + def test__remove_unsupported_archs(self): + config_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + expected_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch i386 ', + 'LDFLAGS': ' -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + suffix = (':' + self.env['PATH']) if self.env['PATH'] else '' + self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix + c_name = 'clang' + test.test_support.unlink(c_name) + self.addCleanup(test.test_support.unlink, c_name) + # exit status 255 means no PPC support in this compiler chain + with open(c_name, 'w') as f: + f.write("#!/bin/sh\nexit 255") + os.chmod(c_name, stat.S_IRWXU) + self.assertEqual(expected_vars, + _osx_support._remove_unsupported_archs( + config_vars)) + + def test__override_all_archs(self): + self.env['ARCHFLAGS'] = '-arch x86_64' + config_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + expected_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch x86_64', + 'LDFLAGS': ' -g -arch x86_64', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -g -arch x86_64', + 'LDSHARED': 'gcc-4.0 -bundle -isysroot ' + '/Developer/SDKs/MacOSX10.4u.sdk -g -arch x86_64', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + self.assertEqual(expected_vars, + _osx_support._override_all_archs( + config_vars)) + + def test__check_for_unavailable_sdk(self): + config_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.1.sdk', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.1.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.1.sdk -g', + } + expected_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ' + ' ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. ', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + ' -g', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + self.assertEqual(expected_vars, + _osx_support._check_for_unavailable_sdk( + config_vars)) + + def test_get_platform_osx(self): + # Note, get_platform_osx is currently tested more extensively + # indirectly by test_sysconfig and test_distutils + config_vars = { + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.1.sdk', + 'MACOSX_DEPLOYMENT_TARGET': '10.6', + } + result = _osx_support.get_platform_osx(config_vars, ' ', ' ', ' ') + self.assertEqual(('macosx', '10.6', 'fat'), result) + +def test_main(): + if sys.platform == 'darwin': + test.test_support.run_unittest(Test_OSXSupport) + +if __name__ == "__main__": + test_main() diff --git a/lib-python/2.7/test/test_file_eintr.py b/lib-python/2.7/test/test_file_eintr.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/test_file_eintr.py @@ -0,0 +1,239 @@ +# Written to test interrupted system calls interfering with our many buffered +# IO implementations. http://bugs.python.org/issue12268 +# +# This tests the '_io' module. Similar tests for Python 2.x's older +# default file I/O implementation exist within test_file2k.py. +# +# It was suggested that this code could be merged into test_io and the tests +# made to work using the same method as the existing signal tests in test_io. +# I was unable to get single process tests using alarm or setitimer that way +# to reproduce the EINTR problems. This process based test suite reproduces +# the problems prior to the issue12268 patch reliably on Linux and OSX. +# - gregory.p.smith + +import os +import select +import signal +import subprocess +import sys +from test.test_support import run_unittest +import time +import unittest + +# Test import all of the things we're about to try testing up front. +from _io import FileIO + + + at unittest.skipUnless(os.name == 'posix', 'tests requires a posix system.') +class TestFileIOSignalInterrupt(unittest.TestCase): + def setUp(self): + self._process = None + + def tearDown(self): + if self._process and self._process.poll() is None: + try: + self._process.kill() + except OSError: + pass + + def _generate_infile_setup_code(self): + """Returns the infile = ... line of code for the reader process. + + subclasseses should override this to test different IO objects. + """ + return ('import _io ;' + 'infile = _io.FileIO(sys.stdin.fileno(), "rb")') + + def fail_with_process_info(self, why, stdout=b'', stderr=b'', + communicate=True): + """A common way to cleanup and fail with useful debug output. + + Kills the process if it is still running, collects remaining output + and fails the test with an error message including the output. + + Args: + why: Text to go after "Error from IO process" in the message. + stdout, stderr: standard output and error from the process so + far to include in the error message. + communicate: bool, when True we call communicate() on the process + after killing it to gather additional output. + """ + if self._process.poll() is None: + time.sleep(0.1) # give it time to finish printing the error. + try: + self._process.terminate() # Ensure it dies. + except OSError: + pass + if communicate: + stdout_end, stderr_end = self._process.communicate() + stdout += stdout_end + stderr += stderr_end + self.fail('Error from IO process %s:\nSTDOUT:\n%sSTDERR:\n%s\n' % + (why, stdout.decode(), stderr.decode())) + + def _test_reading(self, data_to_write, read_and_verify_code): + """Generic buffered read method test harness to validate EINTR behavior. + + Also validates that Python signal handlers are run during the read. + + Args: + data_to_write: String to write to the child process for reading + before sending it a signal, confirming the signal was handled, + writing a final newline and closing the infile pipe. + read_and_verify_code: Single "line" of code to read from a file + object named 'infile' and validate the result. This will be + executed as part of a python subprocess fed data_to_write. + """ + infile_setup_code = self._generate_infile_setup_code() + # Total pipe IO in this function is smaller than the minimum posix OS + # pipe buffer size of 512 bytes. No writer should block. + assert len(data_to_write) < 512, 'data_to_write must fit in pipe buf.' + + # Start a subprocess to call our read method while handling a signal. + self._process = subprocess.Popen( + [sys.executable, '-u', '-c', + 'import io, signal, sys ;' + 'signal.signal(signal.SIGINT, ' + 'lambda s, f: sys.stderr.write("$\\n")) ;' + + infile_setup_code + ' ;' + + 'sys.stderr.write("Worm Sign!\\n") ;' + + read_and_verify_code + ' ;' + + 'infile.close()' + ], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + # Wait for the signal handler to be installed. + worm_sign = self._process.stderr.read(len(b'Worm Sign!\n')) + if worm_sign != b'Worm Sign!\n': # See also, Dune by Frank Herbert. + self.fail_with_process_info('while awaiting a sign', + stderr=worm_sign) + self._process.stdin.write(data_to_write) + + signals_sent = 0 + rlist = [] + # We don't know when the read_and_verify_code in our child is actually + # executing within the read system call we want to interrupt. This + # loop waits for a bit before sending the first signal to increase + # the likelihood of that. Implementations without correct EINTR + # and signal handling usually fail this test. + while not rlist: + rlist, _, _ = select.select([self._process.stderr], (), (), 0.05) + self._process.send_signal(signal.SIGINT) + signals_sent += 1 + if signals_sent > 200: + self._process.kill() + self.fail('reader process failed to handle our signals.') + # This assumes anything unexpected that writes to stderr will also + # write a newline. That is true of the traceback printing code. + signal_line = self._process.stderr.readline() + if signal_line != b'$\n': + self.fail_with_process_info('while awaiting signal', + stderr=signal_line) + + # We append a newline to our input so that a readline call can + # end on its own before the EOF is seen and so that we're testing + # the read call that was interrupted by a signal before the end of + # the data stream has been reached. + stdout, stderr = self._process.communicate(input=b'\n') + if self._process.returncode: + self.fail_with_process_info( + 'exited rc=%d' % self._process.returncode, + stdout, stderr, communicate=False) + # PASS! + + # String format for the read_and_verify_code used by read methods. + _READING_CODE_TEMPLATE = ( + 'got = infile.{read_method_name}() ;' + 'expected = {expected!r} ;' + 'assert got == expected, (' + '"{read_method_name} returned wrong data.\\n"' + '"got data %r\\nexpected %r" % (got, expected))' + ) + + def test_readline(self): + """readline() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello, world!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readline', + expected=b'hello, world!\n')) + + def test_readlines(self): + """readlines() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readlines', + expected=[b'hello\n', b'world!\n'])) + + def test_readall(self): + """readall() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readall', + expected=b'hello\nworld!\n')) + # read() is the same thing as readall(). + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='read', + expected=b'hello\nworld!\n')) + + +class TestBufferedIOSignalInterrupt(TestFileIOSignalInterrupt): + def _generate_infile_setup_code(self): + """Returns the infile = ... line of code to make a BufferedReader.""" + return ('infile = io.open(sys.stdin.fileno(), "rb") ;' + 'import _io ;assert isinstance(infile, _io.BufferedReader)') + + def test_readall(self): + """BufferedReader.read() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='read', + expected=b'hello\nworld!\n')) + + +class TestTextIOSignalInterrupt(TestFileIOSignalInterrupt): + def _generate_infile_setup_code(self): + """Returns the infile = ... line of code to make a TextIOWrapper.""" + return ('infile = io.open(sys.stdin.fileno(), "rt", newline=None) ;' + 'import _io ;assert isinstance(infile, _io.TextIOWrapper)') + + def test_readline(self): + """readline() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello, world!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readline', + expected='hello, world!\n')) + + def test_readlines(self): + """readlines() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\r\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readlines', + expected=['hello\n', 'world!\n'])) + + def test_readall(self): + """read() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='read', + expected="hello\nworld!\n")) + + +def test_main(): + test_cases = [ + tc for tc in globals().values() + if isinstance(tc, type) and issubclass(tc, unittest.TestCase)] + run_unittest(*test_cases) + + +if __name__ == '__main__': + test_main() diff --git a/lib-python/2.7/test/testbz2_bigmem.bz2 b/lib-python/2.7/test/testbz2_bigmem.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..c9a4616c8053b1c92a45023635c8610c26299fc2 GIT binary patch [stripped] -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:51 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:51 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_One_last_add_from_2=2E7_rev?= =?utf-8?q?_82552=3Aa8047d1376d5?= Message-ID: <3ZPmPg2FMnzRTM@mail.python.org> http://hg.python.org/jython/rev/c3af9ed52670 changeset: 7080:c3af9ed52670 user: Frank Wierzbicki date: Sun Mar 10 19:43:37 2013 -0700 summary: One last add from 2.7 rev 82552:a8047d1376d5 files: lib-python/2.7/test/crashers/buffer_mutate.py | 30 ++++++++++ 1 files changed, 30 insertions(+), 0 deletions(-) diff --git a/lib-python/2.7/test/crashers/buffer_mutate.py b/lib-python/2.7/test/crashers/buffer_mutate.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/crashers/buffer_mutate.py @@ -0,0 +1,30 @@ +# +# The various methods of bufferobject.c (here buffer_subscript()) call +# get_buf() before calling potentially more Python code (here via +# PySlice_GetIndicesEx()). But get_buf() already returned a void* +# pointer. This void* pointer can become invalid if the object +# underlying the buffer is mutated (here a bytearray object). +# +# As usual, please keep in mind that the three "here" in the sentence +# above are only examples. Each can be changed easily and lead to +# another crasher. +# +# This crashes for me on Linux 32-bits with CPython 2.6 and 2.7 +# with a segmentation fault. +# + + +class PseudoIndex(object): + def __index__(self): + for c in "foobar"*n: + a.append(c) + return n * 4 + + +for n in range(1, 100000, 100): + a = bytearray("test"*n) + buf = buffer(a) + + s = buf[:PseudoIndex():1] + #print repr(s) + #assert s == "test"*n -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:52 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:52 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_test=5Fhttpservers_t?= =?utf-8?q?o_latest_2=2E7=2E?= Message-ID: <3ZPmPh5yGqzR66@mail.python.org> http://hg.python.org/jython/rev/a438b8f60c96 changeset: 7081:a438b8f60c96 user: Frank Wierzbicki date: Mon Mar 11 09:47:24 2013 -0700 summary: Update test_httpservers to latest 2.7. files: Lib/test/test_httpservers.py | 76 ++++++++++++----------- 1 files changed, 39 insertions(+), 37 deletions(-) diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -4,11 +4,6 @@ Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest. """ -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -from SimpleHTTPServer import SimpleHTTPRequestHandler -from CGIHTTPServer import CGIHTTPRequestHandler -import CGIHTTPServer - import os import sys import re @@ -17,12 +12,17 @@ import urllib import httplib import tempfile +import unittest +import CGIHTTPServer -import unittest +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from SimpleHTTPServer import SimpleHTTPRequestHandler +from CGIHTTPServer import CGIHTTPRequestHandler from StringIO import StringIO +from test import test_support -from test import test_support + threading = test_support.import_module('threading') @@ -43,7 +43,7 @@ self.end_headers() self.wfile.write(b'Data\r\n') - def log_message(self, format, *args): + def log_message(self, fmt, *args): pass @@ -97,9 +97,9 @@ self.handler = SocketlessRequestHandler() def send_typical_request(self, message): - input = StringIO(message) + input_msg = StringIO(message) output = StringIO() - self.handler.rfile = input + self.handler.rfile = input_msg self.handler.wfile = output self.handler.handle_one_request() output.seek(0) @@ -296,7 +296,7 @@ os.chdir(self.cwd) try: shutil.rmtree(self.tempdir) - except: + except OSError: pass finally: BaseTestCase.tearDown(self) @@ -418,42 +418,44 @@ finally: BaseTestCase.tearDown(self) - def test_url_collapse_path_split(self): + def test_url_collapse_path(self): + # verify tail is the last portion and head is the rest on proper urls test_vectors = { - '': ('/', ''), + '': '//', '..': IndexError, '/.//..': IndexError, - '/': ('/', ''), - '//': ('/', ''), - '/\\': ('/', '\\'), - '/.//': ('/', ''), - 'cgi-bin/file1.py': ('/cgi-bin', 'file1.py'), - '/cgi-bin/file1.py': ('/cgi-bin', 'file1.py'), - '/cgi-bin/file1.py/PATH-INFO': ('/cgi-bin', 'file1.py/PATH-INFO'), - 'a': ('/', 'a'), - '/a': ('/', 'a'), - '//a': ('/', 'a'), - './a': ('/', 'a'), - './C:/': ('/C:', ''), - '/a/b': ('/a', 'b'), - '/a/b/': ('/a/b', ''), - '/a/b/c/..': ('/a/b', ''), - '/a/b/c/../d': ('/a/b', 'd'), - '/a/b/c/../d/e/../f': ('/a/b/d', 'f'), - '/a/b/c/../d/e/../../f': ('/a/b', 'f'), - '/a/b/c/../d/e/.././././..//f': ('/a/b', 'f'), + '/': '//', + '//': '//', + '/\\': '//\\', + '/.//': '//', + 'cgi-bin/file1.py': '/cgi-bin/file1.py', + '/cgi-bin/file1.py': '/cgi-bin/file1.py', + 'a': '//a', + '/a': '//a', + '//a': '//a', + './a': '//a', + './C:/': '/C:/', + '/a/b': '/a/b', + '/a/b/': '/a/b/', + '/a/b/.': '/a/b/', + '/a/b/c/..': '/a/b/', + '/a/b/c/../d': '/a/b/d', + '/a/b/c/../d/e/../f': '/a/b/d/f', + '/a/b/c/../d/e/../../f': '/a/b/f', + '/a/b/c/../d/e/.././././..//f': '/a/b/f', '../a/b/c/../d/e/.././././..//f': IndexError, - '/a/b/c/../d/e/../../../f': ('/a', 'f'), - '/a/b/c/../d/e/../../../../f': ('/', 'f'), + '/a/b/c/../d/e/../../../f': '/a/f', + '/a/b/c/../d/e/../../../../f': '//f', '/a/b/c/../d/e/../../../../../f': IndexError, - '/a/b/c/../d/e/../../../../f/..': ('/', ''), + '/a/b/c/../d/e/../../../../f/..': '//', + '/a/b/c/../d/e/../../../../f/../.': '//', } for path, expected in test_vectors.iteritems(): if isinstance(expected, type) and issubclass(expected, Exception): self.assertRaises(expected, - CGIHTTPServer._url_collapse_path_split, path) + CGIHTTPServer._url_collapse_path, path) else: - actual = CGIHTTPServer._url_collapse_path_split(path) + actual = CGIHTTPServer._url_collapse_path(path) self.assertEqual(expected, actual, msg='path = %r\nGot: %r\nWanted: %r' % (path, actual, expected)) -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:54 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:54 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_test=5Fmailbox_and_t?= =?utf-8?q?est=5Fsupport_for_latest_2=2E7=2E?= Message-ID: <3ZPmPk5H4zzRYr@mail.python.org> http://hg.python.org/jython/rev/6f0976c35eaa changeset: 7082:6f0976c35eaa user: Frank Wierzbicki date: Mon Mar 11 10:02:12 2013 -0700 summary: Update test_mailbox and test_support for latest 2.7. files: Lib/test/test_mailbox.py | 208 ++++++++++++++++++++------ Lib/test/test_support.py | 158 +++++++++++++++++++- 2 files changed, 308 insertions(+), 58 deletions(-) diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -8,6 +8,7 @@ import re import shutil import StringIO +import tempfile from test import test_support import unittest import mailbox @@ -20,7 +21,7 @@ # Silence Py3k warning rfc822 = test_support.import_module('rfc822', deprecated=True) -class TestBase(unittest.TestCase): +class TestBase: def _check_sample(self, msg): # Inspect a mailbox.Message representation of the sample message @@ -39,15 +40,15 @@ def _delete_recursively(self, target): # Delete a file or delete a directory recursively if os.path.isdir(target): - shutil.rmtree(target) + test_support.rmtree(target) elif os.path.exists(target): - os.remove(target) + test_support.unlink(target) class TestMailbox(TestBase): _factory = None # Overridden by subclasses to reuse tests - _template = 'From: foo\n\n%s' + _template = 'From: foo\n\n%s\n' def setUp(self): self._path = test_support.TESTFN @@ -75,6 +76,18 @@ for i in (1, 2, 3, 4): self._check_sample(self._box[keys[i]]) + def test_add_file(self): + with tempfile.TemporaryFile('w+') as f: + f.write(_sample_message) + f.seek(0) + key = self._box.add(f) + self.assertEqual(self._box.get_string(key).split('\n'), + _sample_message.split('\n')) + + def test_add_StringIO(self): + key = self._box.add(StringIO.StringIO(self._template % "0")) + self.assertEqual(self._box.get_string(key), self._template % "0") + def test_remove(self): # Remove messages using remove() self._test_remove_or_delitem(self._box.remove) @@ -124,7 +137,7 @@ key0 = self._box.add(self._template % 0) msg = self._box.get(key0) self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.get_payload(), '0') + self.assertEqual(msg.get_payload(), '0\n') self.assertIs(self._box.get('foo'), None) self.assertFalse(self._box.get('foo', False)) self._box.close() @@ -132,14 +145,15 @@ key1 = self._box.add(self._template % 1) msg = self._box.get(key1) self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.fp.read(), '1') + self.assertEqual(msg.fp.read(), '1' + os.linesep) + msg.fp.close() def test_getitem(self): # Retrieve message using __getitem__() key0 = self._box.add(self._template % 0) msg = self._box[key0] self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.get_payload(), '0') + self.assertEqual(msg.get_payload(), '0\n') self.assertRaises(KeyError, lambda: self._box['foo']) self._box.discard(key0) self.assertRaises(KeyError, lambda: self._box[key0]) @@ -151,7 +165,7 @@ msg0 = self._box.get_message(key0) self.assertIsInstance(msg0, mailbox.Message) self.assertEqual(msg0['from'], 'foo') - self.assertEqual(msg0.get_payload(), '0') + self.assertEqual(msg0.get_payload(), '0\n') self._check_sample(self._box.get_message(key1)) def test_get_string(self): @@ -165,10 +179,14 @@ # Get file representations of messages key0 = self._box.add(self._template % 0) key1 = self._box.add(_sample_message) - self.assertEqual(self._box.get_file(key0).read().replace(os.linesep, '\n'), + msg0 = self._box.get_file(key0) + self.assertEqual(msg0.read().replace(os.linesep, '\n'), self._template % 0) - self.assertEqual(self._box.get_file(key1).read().replace(os.linesep, '\n'), + msg1 = self._box.get_file(key1) + self.assertEqual(msg1.read().replace(os.linesep, '\n'), _sample_message) + msg0.close() + msg1.close() def test_get_file_can_be_closed_twice(self): # Issue 11700 @@ -320,15 +338,15 @@ self.assertIn(key0, self._box) key1 = self._box.add(self._template % 1) self.assertIn(key1, self._box) - self.assertEqual(self._box.pop(key0).get_payload(), '0') + self.assertEqual(self._box.pop(key0).get_payload(), '0\n') self.assertNotIn(key0, self._box) self.assertIn(key1, self._box) key2 = self._box.add(self._template % 2) self.assertIn(key2, self._box) - self.assertEqual(self._box.pop(key2).get_payload(), '2') + self.assertEqual(self._box.pop(key2).get_payload(), '2\n') self.assertNotIn(key2, self._box) self.assertIn(key1, self._box) - self.assertEqual(self._box.pop(key1).get_payload(), '1') + self.assertEqual(self._box.pop(key1).get_payload(), '1\n') self.assertNotIn(key1, self._box) self.assertEqual(len(self._box), 0) @@ -386,6 +404,17 @@ # Write changes to disk self._test_flush_or_close(self._box.flush, True) + def test_popitem_and_flush_twice(self): + # See #15036. + self._box.add(self._template % 0) + self._box.add(self._template % 1) + self._box.flush() + + self._box.popitem() + self._box.flush() + self._box.popitem() + self._box.flush() + def test_lock_unlock(self): # Lock and unlock the mailbox self.assertFalse(os.path.exists(self._get_lock_path())) @@ -403,6 +432,7 @@ self._box.add(contents[0]) self._box.add(contents[1]) self._box.add(contents[2]) + oldbox = self._box method() if should_call_close: self._box.close() @@ -411,6 +441,7 @@ self.assertEqual(len(keys), 3) for key in keys: self.assertIn(self._box.get_string(key), contents) + oldbox.close() def test_dump_message(self): # Write message representations to disk @@ -429,7 +460,7 @@ return self._path + '.lock' -class TestMailboxSuperclass(TestBase): +class TestMailboxSuperclass(TestBase, unittest.TestCase): def test_notimplemented(self): # Test that all Mailbox methods raise NotImplementedException. @@ -464,7 +495,7 @@ self.assertRaises(NotImplementedError, lambda: box.close()) -class TestMaildir(TestMailbox): +class TestMaildir(TestMailbox, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.Maildir(path, factory) @@ -506,7 +537,7 @@ msg_returned = self._box.get_message(key) self.assertEqual(msg_returned.get_subdir(), 'new') self.assertEqual(msg_returned.get_flags(), '') - self.assertEqual(msg_returned.get_payload(), '1') + self.assertEqual(msg_returned.get_payload(), '1\n') msg2 = mailbox.MaildirMessage(self._template % 2) msg2.set_info('2,S') self._box[key] = msg2 @@ -514,7 +545,7 @@ msg_returned = self._box.get_message(key) self.assertEqual(msg_returned.get_subdir(), 'new') self.assertEqual(msg_returned.get_flags(), 'S') - self.assertEqual(msg_returned.get_payload(), '3') + self.assertEqual(msg_returned.get_payload(), '3\n') def test_consistent_factory(self): # Add a message. @@ -636,13 +667,13 @@ self.assertTrue(match is not None, "Invalid file name: '%s'" % tail) groups = match.groups() if previous_groups is not None: - self.assertTrue(int(groups[0] >= previous_groups[0]), + self.assertGreaterEqual(int(groups[0]), int(previous_groups[0]), "Non-monotonic seconds: '%s' before '%s'" % (previous_groups[0], groups[0])) - self.assertTrue(int(groups[1] >= previous_groups[1]) or - groups[0] != groups[1], - "Non-monotonic milliseconds: '%s' before '%s'" % - (previous_groups[1], groups[1])) + if int(groups[0]) == int(previous_groups[0]): + self.assertGreaterEqual(int(groups[1]), int(previous_groups[1]), + "Non-monotonic milliseconds: '%s' before '%s'" % + (previous_groups[1], groups[1])) self.assertTrue(int(groups[2]) == pid, "Process ID mismatch: '%s' should be '%s'" % (groups[2], pid)) @@ -813,7 +844,49 @@ self._box._refresh() self.assertTrue(refreshed()) -class _TestMboxMMDF(TestMailbox): + +class _TestSingleFile(TestMailbox): + '''Common tests for single-file mailboxes''' + + def test_add_doesnt_rewrite(self): + # When only adding messages, flush() should not rewrite the + # mailbox file. See issue #9559. + + # Inode number changes if the contents are written to another + # file which is then renamed over the original file. So we + # must check that the inode number doesn't change. + inode_before = os.stat(self._path).st_ino + + self._box.add(self._template % 0) + self._box.flush() + + inode_after = os.stat(self._path).st_ino + self.assertEqual(inode_before, inode_after) + + # Make sure the message was really added + self._box.close() + self._box = self._factory(self._path) + self.assertEqual(len(self._box), 1) + + def test_permissions_after_flush(self): + # See issue #5346 + + # Make the mailbox world writable. It's unlikely that the new + # mailbox file would have these permissions after flush(), + # because umask usually prevents it. + mode = os.stat(self._path).st_mode | 0o666 + os.chmod(self._path, mode) + + self._box.add(self._template % 0) + i = self._box.add(self._template % 1) + # Need to remove one message to make flush() create a new file + self._box.remove(i) + self._box.flush() + + self.assertEqual(os.stat(self._path).st_mode, mode) + + +class _TestMboxMMDF(_TestSingleFile): def tearDown(self): self._box.close() @@ -823,14 +896,14 @@ def test_add_from_string(self): # Add a string starting with 'From ' to the mailbox - key = self._box.add('From foo at bar blah\nFrom: foo\n\n0') + key = self._box.add('From foo at bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo at bar blah') - self.assertEqual(self._box[key].get_payload(), '0') + self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_mbox_or_mmdf_message(self): # Add an mboxMessage or MMDFMessage for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): - msg = class_('From foo at bar blah\nFrom: foo\n\n0') + msg = class_('From foo at bar blah\nFrom: foo\n\n0\n') key = self._box.add(msg) def test_open_close_open(self): @@ -914,7 +987,7 @@ self._box.close() -class TestMbox(_TestMboxMMDF): +class TestMbox(_TestMboxMMDF, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.mbox(path, factory) @@ -937,12 +1010,35 @@ perms = st.st_mode self.assertFalse((perms & 0111)) # Execute bits should all be off. -class TestMMDF(_TestMboxMMDF): + def test_terminating_newline(self): + message = email.message.Message() + message['From'] = 'john at example.com' + message.set_payload('No newline at the end') + i = self._box.add(message) + + # A newline should have been appended to the payload + message = self._box.get(i) + self.assertEqual(message.get_payload(), 'No newline at the end\n') + + def test_message_separator(self): + # Check there's always a single blank line after each message + self._box.add('From: foo\n\n0') # No newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + self._box.add('From: foo\n\n0\n') # Newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + +class TestMMDF(_TestMboxMMDF, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.MMDF(path, factory) -class TestMH(TestMailbox): +class TestMH(TestMailbox, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.MH(path, factory) @@ -1074,7 +1170,7 @@ return os.path.join(self._path, '.mh_sequences.lock') -class TestBabyl(TestMailbox): +class TestBabyl(_TestSingleFile, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.Babyl(path, factory) @@ -1103,7 +1199,7 @@ self.assertEqual(set(self._box.get_labels()), set(['blah'])) -class TestMessage(TestBase): +class TestMessage(TestBase, unittest.TestCase): _factory = mailbox.Message # Overridden by subclasses to reuse tests @@ -1174,7 +1270,7 @@ pass -class TestMaildirMessage(TestMessage): +class TestMaildirMessage(TestMessage, unittest.TestCase): _factory = mailbox.MaildirMessage @@ -1249,7 +1345,7 @@ self._check_sample(msg) -class _TestMboxMMDFMessage(TestMessage): +class _TestMboxMMDFMessage: _factory = mailbox._mboxMMDFMessage @@ -1296,12 +1392,12 @@ r"\d{2} \d{4}", msg.get_from())) -class TestMboxMessage(_TestMboxMMDFMessage): +class TestMboxMessage(_TestMboxMMDFMessage, TestMessage): _factory = mailbox.mboxMessage -class TestMHMessage(TestMessage): +class TestMHMessage(TestMessage, unittest.TestCase): _factory = mailbox.MHMessage @@ -1332,7 +1428,7 @@ self.assertEqual(msg.get_sequences(), ['foobar', 'replied']) -class TestBabylMessage(TestMessage): +class TestBabylMessage(TestMessage, unittest.TestCase): _factory = mailbox.BabylMessage @@ -1387,12 +1483,12 @@ self.assertEqual(visible[header], msg[header]) -class TestMMDFMessage(_TestMboxMMDFMessage): +class TestMMDFMessage(_TestMboxMMDFMessage, TestMessage): _factory = mailbox.MMDFMessage -class TestMessageConversion(TestBase): +class TestMessageConversion(TestBase, unittest.TestCase): def test_plain_to_x(self): # Convert Message to all formats @@ -1715,7 +1811,7 @@ proxy.close() -class TestProxyFile(TestProxyFileBase): +class TestProxyFile(TestProxyFileBase, unittest.TestCase): def setUp(self): self._path = test_support.TESTFN @@ -1764,7 +1860,7 @@ self._test_close(mailbox._ProxyFile(self._file)) -class TestPartialFile(TestProxyFileBase): +class TestPartialFile(TestProxyFileBase, unittest.TestCase): def setUp(self): self._path = test_support.TESTFN @@ -1831,6 +1927,10 @@ def setUp(self): # create a new maildir mailbox to work with: self._dir = test_support.TESTFN + if os.path.isdir(self._dir): + test_support.rmtree(self._dir) + if os.path.isfile(self._dir): + test_support.unlink(self._dir) os.mkdir(self._dir) os.mkdir(os.path.join(self._dir, "cur")) os.mkdir(os.path.join(self._dir, "tmp")) @@ -1840,10 +1940,10 @@ def tearDown(self): map(os.unlink, self._msgfiles) - os.rmdir(os.path.join(self._dir, "cur")) - os.rmdir(os.path.join(self._dir, "tmp")) - os.rmdir(os.path.join(self._dir, "new")) - os.rmdir(self._dir) + test_support.rmdir(os.path.join(self._dir, "cur")) + test_support.rmdir(os.path.join(self._dir, "tmp")) + test_support.rmdir(os.path.join(self._dir, "new")) + test_support.rmdir(self._dir) def createMessage(self, dir, mbox=False): t = int(time.time() % 1000000) @@ -1879,7 +1979,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1887,7 +1989,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1896,8 +2000,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 2) - self.assertIsNot(self.mbox.next(), None) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1906,11 +2014,13 @@ import email.parser fname = self.createMessage("cur", True) n = 0 - for msg in mailbox.PortableUnixMailbox(open(fname), + fid = open(fname) + for msg in mailbox.PortableUnixMailbox(fid, email.parser.Parser().parse): n += 1 self.assertEqual(msg["subject"], "Simple Test") self.assertEqual(len(str(msg)), len(FROM_)+len(DUMMY_MESSAGE)) + fid.close() self.assertEqual(n, 1) ## End: classes from the original module (for backward compatibility). diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -18,6 +18,14 @@ import UserDict import re import time +import struct +import sysconfig + +try: + import _testcapi +except ImportError: + _testcapi = None + try: import thread except ImportError: @@ -181,15 +189,79 @@ except KeyError: pass +if sys.platform.startswith("win"): + def _waitfor(func, pathname, waitall=False): + # Peform the operation + func(pathname) + # Now setup the wait loop + if waitall: + dirname = pathname + else: + dirname, name = os.path.split(pathname) + dirname = dirname or '.' + # Check for `pathname` to be removed from the filesystem. + # The exponential backoff of the timeout amounts to a total + # of ~1 second after which the deletion is probably an error + # anyway. + # Testing on a i7 at 4.3GHz shows that usually only 1 iteration is + # required when contention occurs. + timeout = 0.001 + while timeout < 1.0: + # Note we are only testing for the existance of the file(s) in + # the contents of the directory regardless of any security or + # access rights. If we have made it this far, we have sufficient + # permissions to do that much using Python's equivalent of the + # Windows API FindFirstFile. + # Other Windows APIs can fail or give incorrect results when + # dealing with files that are pending deletion. + L = os.listdir(dirname) + if not (L if waitall else name in L): + return + # Increase the timeout and try again + time.sleep(timeout) + timeout *= 2 + warnings.warn('tests may fail, delete still pending for ' + pathname, + RuntimeWarning, stacklevel=4) + + def _unlink(filename): + _waitfor(os.unlink, filename) + + def _rmdir(dirname): + _waitfor(os.rmdir, dirname) + + def _rmtree(path): + def _rmtree_inner(path): + for name in os.listdir(path): + fullname = os.path.join(path, name) + if os.path.isdir(fullname): + _waitfor(_rmtree_inner, fullname, waitall=True) + os.rmdir(fullname) + else: + os.unlink(fullname) + _waitfor(_rmtree_inner, path, waitall=True) + _waitfor(os.rmdir, path) +else: + _unlink = os.unlink + _rmdir = os.rmdir + _rmtree = shutil.rmtree + def unlink(filename): try: - os.unlink(filename) + _unlink(filename) except OSError: pass +def rmdir(dirname): + try: + _rmdir(dirname) + except OSError as error: + # The directory need not exist. + if error.errno != errno.ENOENT: + raise + def rmtree(path): try: - shutil.rmtree(path) + _rmtree(path) except OSError, e: # Unix returns ENOENT, Windows returns ESRCH. if e.errno not in (errno.ENOENT, errno.ESRCH): @@ -465,7 +537,7 @@ the CWD, an error is raised. If it's True, only a warning is raised and the original CWD is used. """ - if isinstance(name, unicode): + if have_unicode and isinstance(name, unicode): try: name = name.encode(sys.getfilesystemencoding() or 'ascii') except UnicodeEncodeError: @@ -813,6 +885,9 @@ ('EAI_FAIL', -4), ('EAI_NONAME', -2), ('EAI_NODATA', -5), + # Windows defines EAI_NODATA as 11001 but idiotic getaddrinfo() + # implementation actually returns WSANO_DATA i.e. 11004. + ('WSANO_DATA', 11004), ] denied = ResourceDenied("Resource '%s' is not available" % resource_name) @@ -886,6 +961,33 @@ return captured_output("stdin") +_header = '2P' +if hasattr(sys, "gettotalrefcount"): + _header = '2P' + _header +_vheader = _header + 'P' + +def calcobjsize(fmt): + return struct.calcsize(_header + fmt + '0P') + +def calcvobjsize(fmt): + return struct.calcsize(_vheader + fmt + '0P') + + +_TPFLAGS_HAVE_GC = 1<<14 +_TPFLAGS_HEAPTYPE = 1<<9 + +def check_sizeof(test, o, size): + result = sys.getsizeof(o) + # add GC header size + if (_testcapi and\ + (type(o) == type) and (o.__flags__ & _TPFLAGS_HEAPTYPE) or\ + ((type(o) != type) and (type(o).__flags__ & _TPFLAGS_HAVE_GC))): + size += _testcapi.SIZEOF_PYGC_HEAD + msg = 'wrong size for %s: got %d, expected %d' \ + % (type(o), result, size) + test.assertEqual(result, size, msg) + + #======================================================================= # Decorator for running a function in a different locale, correctly resetting # it afterwards. @@ -994,7 +1096,7 @@ return wrapper return decorator -def precisionbigmemtest(size, memuse, overhead=5*_1M): +def precisionbigmemtest(size, memuse, overhead=5*_1M, dry_run=True): def decorator(f): def wrapper(self): if not real_max_memuse: @@ -1002,11 +1104,12 @@ else: maxsize = size - if real_max_memuse and real_max_memuse < maxsize * memuse: - if verbose: - sys.stderr.write("Skipping %s because of memory " - "constraint\n" % (f.__name__,)) - return + if ((real_max_memuse or not dry_run) + and real_max_memuse < maxsize * memuse): + if verbose: + sys.stderr.write("Skipping %s because of memory " + "constraint\n" % (f.__name__,)) + return return f(self, maxsize) wrapper.size = size @@ -1144,6 +1247,16 @@ suite.addTest(unittest.makeSuite(cls)) _run_suite(suite) +#======================================================================= +# Check for the presence of docstrings. + +HAVE_DOCSTRINGS = (check_impl_detail(cpython=False) or + sys.platform == 'win32' or + sysconfig.get_config_var('WITH_DOC_STRINGS')) + +requires_docstrings = unittest.skipUnless(HAVE_DOCSTRINGS, + "test requires docstrings") + #======================================================================= # doctest driver. @@ -1231,6 +1344,33 @@ except: break + at contextlib.contextmanager +def swap_attr(obj, attr, new_val): + """Temporary swap out an attribute with a new object. + + Usage: + with swap_attr(obj, "attr", 5): + ... + + This will set obj.attr to 5 for the duration of the with: block, + restoring the old value at the end of the block. If `attr` doesn't + exist on `obj`, it will be created and then deleted at the end of the + block. + """ + if hasattr(obj, attr): + real_val = getattr(obj, attr) + setattr(obj, attr, new_val) + try: + yield + finally: + setattr(obj, attr, real_val) + else: + setattr(obj, attr, new_val) + try: + yield + finally: + delattr(obj, attr) + def py3k_bytes(b): """Emulate the py3k bytes() constructor. -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Mon Mar 11 18:23:56 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Mon, 11 Mar 2013 18:23:56 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_forward=2E?= Message-ID: <3ZPmPm1wDBzRXh@mail.python.org> http://hg.python.org/jython/rev/358986e4d053 changeset: 7083:358986e4d053 parent: 7082:6f0976c35eaa parent: 7073:e7c373ed9da2 user: Frank Wierzbicki date: Mon Mar 11 10:23:04 2013 -0700 summary: Merge forward. files: Lib/_jyio.py | 112 ++++++++++++++++++++++++- Lib/socket.py | 6 + Lib/test/test_memoryio.py | 20 ---- 3 files changed, 110 insertions(+), 28 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -275,10 +275,48 @@ self._buffer = buf self._pos = 0 + # Jython: modelled after bytesio.c::bytesio_getstate def __getstate__(self): - if self.closed: - raise ValueError("__getstate__ on closed file") - return self.__dict__.copy() + d = getattr(self, '__dict__', None) + if d is not None : + d = d.copy() + return (self.getvalue(), self._pos, d) + + # Jython: modelled after bytesio.c::bytesio_setstate + def __setstate__(self, state): + + if not isinstance(state, tuple) or len(state) < 3 : + fmt = "%s.__setstate__ argument should be 3-tuple got %s" + raise TypeError( fmt % (type(self), type(state)) ) + + # Reset the object to its default state. This is only needed to handle + # the case of repeated calls to __setstate__. */ + self._buffer = bytearray() + self._pos = 0 + + # Set the value of the internal buffer. If state[0] does not support the + # buffer protocol, bytesio_write will raise the appropriate TypeError. */ + self.write(state[0]); + + # Carefully set the position value. Alternatively, we could use the seek + # method instead of modifying self._pos directly to better protect the + # object internal state against erroneous (or malicious) inputs. */ + p = state[1] + if not isinstance(p, (int, long)) : + fmt = "second item of state must be an integer, got %s" + raise TypeError( fmt % type(p) ) + elif p < 0 : + raise ValueError("position value cannot be negative") + self._pos = p + + # Set the dictionary of the instance variables. */ + d = state[2] + if not d is None : + if isinstance(d, dict) : + self.__dict__ = d + else : + fmt = "third item of state should be a dict, got %s" + raise TypeError( fmt % type(d) ) def getvalue(self): """Return the bytes value (contents) of the buffer @@ -1479,6 +1517,11 @@ """ def __init__(self, initial_value="", newline="\n"): + + # Newline mark needs to be in bytes: convert if not already so + if isinstance(newline, unicode) : + newline = newline.encode("utf-8") + super(StringIO, self).__init__(BytesIO(), encoding="utf-8", errors="strict", @@ -1487,11 +1530,64 @@ # C version, even under Windows. if newline is None: self._writetranslate = False - if initial_value: - if not isinstance(initial_value, unicode): - initial_value = unicode(initial_value) - self.write(initial_value) - self.seek(0) + # An initial value may have been supplied (and must be unicode) + if initial_value is not None: + if not isinstance(initial_value, unicode) : + fmt = "initial value should be unicode or None, got %s" + raise TypeError( fmt % type(initial_value) ) + if initial_value: + self.write(initial_value) + self.seek(0) + + # Jython: modelled after stringio.c::stringio_getstate + def __getstate__(self): + d = getattr(self, '__dict__', None) + if d is not None : + d = d.copy() + return (self.getvalue(), self._readnl, self.tell(), d) + + # Jython: modelled after stringio.c:stringio_setstate + def __setstate__(self, state): + self._checkClosed() + + if not isinstance(state, tuple) or len(state) < 4 : + fmt = "%s.__setstate__ argument should be 4-tuple got %s" + raise TypeError( fmt % (type(self), type(state)) ) + + # Initialize the object's state, but empty + self.__init__(None, state[1]) + + # Write the buffer, bypassing end-of-line translation. + value = state[0] + if value is not None: + if not isinstance(value, unicode) : + fmt = "ivalue should be unicode or None, got %s" + raise TypeError( fmt % type(value) ) + encoder = self._encoder or self._get_encoder() + b = encoder.encode(state[0]) + self.buffer.write(b) + + # Reset the object to its default state. This is only needed to handle + # the case of repeated calls to __setstate__. + self.seek(0) + + # Set the position value using seek. A long is tolerated (e.g from pickle). + p = state[2] + if not isinstance(p, (int, long)) : + fmt = "third item of state must be an integer, got %s" + raise TypeError( fmt % type(p) ) + elif p < 0 : + raise ValueError("position value cannot be negative") + self.seek(p) + + # Set the dictionary of the instance variables. */ + d = state[3] + if not d is None : + if isinstance(d, dict) : + self.__dict__ = d + else : + fmt = "fourth item of state should be a dict, got %s" + raise TypeError( fmt % type(d) ) def getvalue(self): self.flush() diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -1856,6 +1856,9 @@ send = sendall = write + def makefile(self, mode='r', bufsize=-1): + return _fileobject(self, mode, bufsize) + def _get_server_cert(self): return self.java_ssl_socket.getSession().getPeerCertificates()[0] @@ -1869,6 +1872,9 @@ cert = self._get_server_cert() return cert.getIssuerDN().toString() + def close(self): + self.jython_socket_wrapper.close() + def test(): s = socket(AF_INET, SOCK_STREAM) s.connect(("", 80)) diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -629,11 +629,6 @@ if support.is_jython: # FIXME: Jython issue 1996 test_detach = MemoryTestMixin.test_detach - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_getstate(self): memio = self.ioclass() state = memio.__getstate__() @@ -644,11 +639,6 @@ memio.close() self.assertRaises(ValueError, memio.__getstate__) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_setstate(self): # This checks whether __setstate__ does proper input validation. memio = self.ioclass() @@ -720,11 +710,6 @@ self.assertEqual(memio.write(buf), len(buf)) self.assertEqual(memio.getvalue(), buf + buf) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_getstate(self): memio = self.ioclass() state = memio.__getstate__() @@ -736,11 +721,6 @@ memio.close() self.assertRaises(ValueError, memio.__getstate__) - # This test isn't working on Ubuntu on an Apple Intel powerbook, - # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) - # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 - @unittest.skipIf(support.is_jython, - "FIXME: Currently not working on jython") def test_setstate(self): # This checks whether __setstate__ does proper input validation. memio = self.ioclass() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Wed Mar 13 19:20:20 2013 From: jython-checkins at python.org (alan.kennedy) Date: Wed, 13 Mar 2013 19:20:20 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Re-enabling_a_test_that_was?= =?utf-8?q?_fixed_by_the_recent_cpython_2=2E7_library_update?= Message-ID: <3ZR1Yw0YWszPGH@mail.python.org> http://hg.python.org/jython/rev/02338aff889b changeset: 7084:02338aff889b user: Alan Kennedy date: Wed Mar 13 18:17:58 2013 +0000 summary: Re-enabling a test that was fixed by the recent cpython 2.7 library update files: Lib/test/test_urllib2.py | 1 - 1 files changed, 0 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -1117,7 +1117,6 @@ def test_basic_auth_with_single_quoted_realm(self): self.test_basic_auth(quote_char="'") - @unittest.skipIf(test_support.is_jython, "Currently not working on jython") def test_basic_auth_with_unquoted_realm(self): opener = OpenerDirector() password_manager = MockPasswordManager() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 15 01:17:24 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 15 Mar 2013 01:17:24 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fix_regressions_in_=5Fjyio_?= =?utf-8?q?following_lib_2=2E7_update?= Message-ID: <3ZRnRS6V6PzR0x@mail.python.org> http://hg.python.org/jython/rev/039dba919d92 changeset: 7085:039dba919d92 user: Jeff Allen date: Fri Mar 15 00:01:22 2013 +0000 summary: Fix regressions in _jyio following lib 2.7 update test.test_memoryio changed to match changes in _pyio.py. Our _jyio.py and local version of test_memoryio were upgraded to correspond. (TextIOWrapper and BytesIO gain tests for closed.) files: Lib/_jyio.py | 14 ++++++++++---- Lib/test/test_memoryio.py | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -405,12 +405,15 @@ return pos def readable(self): + self._checkClosed() return True def writable(self): + self._checkClosed() return True def seekable(self): + self._checkClosed() return True @@ -1080,6 +1083,7 @@ def seekable(self): self._checkInitialized() # Jython: to forbid use in an invalid state + self._checkClosed() return self._seekable def readable(self): @@ -1097,10 +1101,12 @@ def close(self): if self.buffer is not None and not self.closed: - # Jython difference: flush and close via super. - # Sets __closed for quick _checkClosed(). - super(TextIOWrapper, self).close() - self.buffer.close() + try: + # Jython difference: flush and close via super. + # Sets __closed for quick _checkClosed(). + super(TextIOWrapper, self).close() + finally: + self.buffer.close() # Jython difference: @property closed(self) inherited from _IOBase.__closed diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py --- a/Lib/test/test_memoryio.py +++ b/Lib/test/test_memoryio.py @@ -328,9 +328,9 @@ self.assertEqual(memio.isatty(), False) self.assertEqual(memio.closed, False) memio.close() - self.assertEqual(memio.writable(), True) - self.assertEqual(memio.readable(), True) - self.assertEqual(memio.seekable(), True) + self.assertRaises(ValueError, memio.writable) + self.assertRaises(ValueError, memio.readable) + self.assertRaises(ValueError, memio.seekable) self.assertRaises(ValueError, memio.isatty) self.assertEqual(memio.closed, True) @@ -655,6 +655,16 @@ memio.close() self.assertRaises(ValueError, memio.__setstate__, (b"closed", 0, None)) + check_sizeof = support.check_sizeof + + @support.cpython_only + def test_sizeof(self): + basesize = support.calcobjsize(b'P2PP2P') + check = self.check_sizeof + self.assertEqual(object.__sizeof__(io.BytesIO()), basesize) + check(io.BytesIO(), basesize ) + check(io.BytesIO(b'a'), basesize + 1 + 1 ) + check(io.BytesIO(b'a' * 1000), basesize + 1000 + 1 ) class CStringIOTest(PyStringIOTest): ioclass = io.StringIO -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Mar 19 01:13:01 2013 From: jython-checkins at python.org (jim.baker) Date: Tue, 19 Mar 2013 01:13:01 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Support_f=2Ehex=28=29_and_c?= =?utf-8?q?orresponding_test=5Fstrtod?= Message-ID: <3ZVF8Y2d65zPMq@mail.python.org> http://hg.python.org/jython/rev/9527e08af81d changeset: 7086:9527e08af81d parent: 6995:a81d0ea19f7b user: Jim Baker date: Mon Mar 18 16:51:13 2013 -0700 summary: Support f.hex() and corresponding test_strtod files: lib-python/2.7/test/test_strtod.py | 65 +++++++------ src/org/python/core/PyFloat.java | 49 +++++++++- src/org/python/core/PySystemState.java | 3 +- 3 files changed, 83 insertions(+), 34 deletions(-) diff --git a/lib-python/2.7/test/test_strtod.py b/lib-python/2.7/test/test_strtod.py --- a/lib-python/2.7/test/test_strtod.py +++ b/lib-python/2.7/test/test_strtod.py @@ -281,21 +281,24 @@ '6213413350821416312194420007991306908470147322020121018368e0', # incorrect lsb detection for round-half-to-even when # bc->scale != 0 (issue 7632, bug 6). - '104308485241983990666713401708072175773165034278685' #... - '682646111762292409330928739751702404658197872319129' #... - '036519947435319418387839758990478549477777586673075' #... - '945844895981012024387992135617064532141489278815239' #... - '849108105951619997829153633535314849999674266169258' #... - '928940692239684771590065027025835804863585454872499' #... - '320500023126142553932654370362024104462255244034053' #... - '203998964360882487378334860197725139151265590832887' #... - '433736189468858614521708567646743455601905935595381' #... - '852723723645799866672558576993978025033590728687206' #... - '296379801363024094048327273913079612469982585674824' #... - '156000783167963081616214710691759864332339239688734' #... - '656548790656486646106983450809073750535624894296242' #... - '072010195710276073042036425579852459556183541199012' #... - '652571123898996574563824424330960027873516082763671875e-1075', + + # Java does not correctly handle, so we don't either + # '104308485241983990666713401708072175773165034278685' #... + # '682646111762292409330928739751702404658197872319129' #... + # '036519947435319418387839758990478549477777586673075' #... + # '945844895981012024387992135617064532141489278815239' #... + # '849108105951619997829153633535314849999674266169258' #... + # '928940692239684771590065027025835804863585454872499' #... + # '320500023126142553932654370362024104462255244034053' #... + # '203998964360882487378334860197725139151265590832887' #... + # '433736189468858614521708567646743455601905935595381' #... + # '852723723645799866672558576993978025033590728687206' #... + # '296379801363024094048327273913079612469982585674824' #... + # '156000783167963081616214710691759864332339239688734' #... + # '656548790656486646106983450809073750535624894296242' #... + # '072010195710276073042036425579852459556183541199012' #... + # '652571123898996574563824424330960027873516082763671875e-1075', + # demonstration that original fix for issue 7632 bug 1 was # buggy; the exit condition was too strong '247032822920623295e-341', @@ -317,21 +320,23 @@ '10.900000000000000012345678912345678912345', # two humongous values from issue 7743 - '116512874940594195638617907092569881519034793229385' #... - '228569165191541890846564669771714896916084883987920' #... - '473321268100296857636200926065340769682863349205363' #... - '349247637660671783209907949273683040397979984107806' #... - '461822693332712828397617946036239581632976585100633' #... - '520260770761060725403904123144384571612073732754774' #... - '588211944406465572591022081973828448927338602556287' #... - '851831745419397433012491884869454462440536895047499' #... - '436551974649731917170099387762871020403582994193439' #... - '761933412166821484015883631622539314203799034497982' #... - '130038741741727907429575673302461380386596501187482' #... - '006257527709842179336488381672818798450229339123527' #... - '858844448336815912020452294624916993546388956561522' #... - '161875352572590420823607478788399460162228308693742' #... - '05287663441403533948204085390898399055004119873046875e-1075', + + # Java does not correctly handle, so we don't either + # '116512874940594195638617907092569881519034793229385' #... + # '228569165191541890846564669771714896916084883987920' #... + # '473321268100296857636200926065340769682863349205363' #... + # '349247637660671783209907949273683040397979984107806' #... + # '461822693332712828397617946036239581632976585100633' #... + # '520260770761060725403904123144384571612073732754774' #... + # '588211944406465572591022081973828448927338602556287' #... + # '851831745419397433012491884869454462440536895047499' #... + # '436551974649731917170099387762871020403582994193439' #... + # '761933412166821484015883631622539314203799034497982' #... + # '130038741741727907429575673302461380386596501187482' #... + # '006257527709842179336488381672818798450229339123527' #... + # '858844448336815912020452294624916993546388956561522' #... + # '161875352572590420823607478788399460162228308693742' #... + # '05287663441403533948204085390898399055004119873046875e-1075', '525440653352955266109661060358202819561258984964913' #... '892256527849758956045218257059713765874251436193619' #... diff --git a/src/org/python/core/PyFloat.java b/src/org/python/core/PyFloat.java --- a/src/org/python/core/PyFloat.java +++ b/src/org/python/core/PyFloat.java @@ -157,9 +157,52 @@ } } - @ExposedClassMethod(doc = BuiltinDocs.float_hex_doc) - public static PyObject float_hex(PyType type, double value) { - return new PyString(Double.toHexString(value)); + // @ExposedClassMethod(doc = BuiltinDocs.float_hex_doc) + // public static PyObject float_hex(PyType type, double value) { + // return new PyString(Double.toHexString(value)); + // } + + private String pyHexString(Double f) { + // Simply rewrite Java hex repr to expected Python values; not + // the most efficient, but we don't expect this to be a hot + // spot in our code either + String java_hex = Double.toHexString(getValue()); + if (java_hex.equals("Infinity")) return "inf"; + if (java_hex.equals("-Infinity")) return "-inf"; + if (java_hex.equals("NaN")) return "nan"; + if (java_hex.equals("0x0.0p0")) return "0x0.0p+0"; + if (java_hex.equals("-0x0.0p0")) return "-0x0.0p+0"; + + // replace hex rep of MpE to conform with Python such that + // 1. M is padded to 16 digits (ignoring a leading -) + // 2. Mp+E if E>=0 + // example: result of 42.0.hex() is translated from + // 0x1.5p5 to 0x1.5000000000000p+5 + int len = java_hex.length(); + boolean start_exponent = false; + StringBuilder py_hex = new StringBuilder(len + 1); + int padding = f > 0 ? 17 : 18; + for (int i=0; i < len; i++) { + char c = java_hex.charAt(i); + if (c == 'p') { + for (int pad=i; pad < padding; pad++) { + py_hex.append('0'); + } + start_exponent = true; + } else if (start_exponent) { + if (c != '-') { + py_hex.append('+'); + } + start_exponent = false; + } + py_hex.append(c); + } + return py_hex.toString(); + } + + @ExposedMethod(doc = BuiltinDocs.float_hex_doc) + public PyObject float_hex() { + return new PyString(pyHexString(getValue())); } /** diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java --- a/src/org/python/core/PySystemState.java +++ b/src/org/python/core/PySystemState.java @@ -80,6 +80,8 @@ // for tests that would need to pass but today would not. public final static int maxsize = Integer.MAX_VALUE; + public final static PyString float_repr_style = Py.newString("short"); + public static boolean py3kwarning = false; public final static Class flags = Options.class; @@ -1448,7 +1450,6 @@ } } } - } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Mar 19 01:13:10 2013 From: jython-checkins at python.org (jim.baker) Date: Tue, 19 Mar 2013 01:13:10 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?b?KTogTWVyZ2Vk?= Message-ID: <3ZVF8k4H5vzP8p@mail.python.org> http://hg.python.org/jython/rev/81c754c55d41 changeset: 7087:81c754c55d41 parent: 7086:9527e08af81d parent: 7085:039dba919d92 user: Jim Baker date: Mon Mar 18 16:53:18 2013 -0700 summary: Merged files: .hgtags | 2 + ACKNOWLEDGMENTS | 4 + Lib/_jyio.py | 132 +- Lib/_warnings.py | 388 --- Lib/decimal.py | 8 +- Lib/doctest.py | 17 +- Lib/platform.py | 162 +- Lib/socket.py | 71 +- Lib/test/string_tests.py | 8 +- Lib/test/test_charmapcodec.py | 61 + Lib/test/test_codecs.py | 2 +- Lib/test/test_collections.py | 7 +- Lib/test/test_csv.py | 1072 ---------- Lib/test/test_decimal.py | 12 + Lib/test/test_dict_jy.py | 7 + Lib/test/test_httpservers.py | 76 +- Lib/test/test_io.py | 49 +- Lib/test/test_io_jy.py | 66 - Lib/test/test_mailbox.py | 208 +- Lib/test/test_memoryio.py | 770 +++++++ Lib/test/test_platform.py | 2 + Lib/test/test_site.py | 1 - Lib/test/test_support.py | 158 +- Lib/test/test_threading_jy.py | 9 + Lib/test/test_urllib2.py | 6 +- Lib/test/test_warnings.py | 11 +- Lib/test/test_zlib.py | 93 +- Lib/zlib.py | 19 +- NEWS | 29 +- README.txt | 22 +- build.xml | 129 +- extlibs/svnant-jars/svnClientAdapter.jar | Bin extlibs/svnant-jars/svnant.jar | Bin extlibs/svnant-jars/svnjavahl.jar | Bin installer/src/java/org/apache/LICENSE.txt | 202 + installer/src/java/org/apache/commons/cli/AlreadySelectedException.java | 81 + installer/src/java/org/apache/commons/cli/BasicParser.java | 92 + installer/src/java/org/apache/commons/cli/CommandLine.java | 328 +++ installer/src/java/org/apache/commons/cli/CommandLineParser.java | 97 + installer/src/java/org/apache/commons/cli/GnuParser.java | 187 + installer/src/java/org/apache/commons/cli/HelpFormatter.java | 542 +++++ installer/src/java/org/apache/commons/cli/MissingArgumentException.java | 82 + installer/src/java/org/apache/commons/cli/MissingOptionException.java | 81 + installer/src/java/org/apache/commons/cli/Option.java | 575 +++++ installer/src/java/org/apache/commons/cli/OptionBuilder.java | 368 +++ installer/src/java/org/apache/commons/cli/OptionGroup.java | 187 + installer/src/java/org/apache/commons/cli/Options.java | 331 +++ installer/src/java/org/apache/commons/cli/ParseException.java | 82 + installer/src/java/org/apache/commons/cli/Parser.java | 282 ++ installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java | 204 + installer/src/java/org/apache/commons/cli/PosixParser.java | 342 +++ installer/src/java/org/apache/commons/cli/TypeHandler.java | 252 ++ installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java | 82 + installer/src/java/org/python/util/install/AbstractWizard.java | 438 ++++ installer/src/java/org/python/util/install/AbstractWizardHeader.java | 12 + installer/src/java/org/python/util/install/AbstractWizardPage.java | 153 + installer/src/java/org/python/util/install/AbstractWizardValidator.java | 132 + installer/src/java/org/python/util/install/ChildProcess.java | 357 +++ installer/src/java/org/python/util/install/ConsoleInstaller.java | 609 +++++ installer/src/java/org/python/util/install/DirectoryFilter.java | 23 + installer/src/java/org/python/util/install/DirectorySelectionPage.java | 200 + installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java | 36 + installer/src/java/org/python/util/install/EmptyValidator.java | 8 + installer/src/java/org/python/util/install/FileHelper.java | 208 + installer/src/java/org/python/util/install/FrameInstaller.java | 186 + installer/src/java/org/python/util/install/Installation.java | 439 ++++ installer/src/java/org/python/util/install/InstallationCancelledException.java | 9 + installer/src/java/org/python/util/install/InstallationListener.java | 7 + installer/src/java/org/python/util/install/InstallationType.java | 120 + installer/src/java/org/python/util/install/InstallerCommandLine.java | 456 ++++ installer/src/java/org/python/util/install/InstallerException.java | 21 + installer/src/java/org/python/util/install/JarInfo.java | 235 ++ installer/src/java/org/python/util/install/JarInstaller.java | 283 ++ installer/src/java/org/python/util/install/JavaHomeHandler.java | 209 + installer/src/java/org/python/util/install/JavaSelectionPage.java | 199 + installer/src/java/org/python/util/install/JavaSelectionPageValidator.java | 31 + installer/src/java/org/python/util/install/JavaVersionTester.java | 61 + installer/src/java/org/python/util/install/LanguagePage.java | 121 + installer/src/java/org/python/util/install/LicensePage.java | 120 + installer/src/java/org/python/util/install/LicensePageValidator.java | 17 + installer/src/java/org/python/util/install/OverviewPage.java | 243 ++ installer/src/java/org/python/util/install/ProgressListener.java | 15 + installer/src/java/org/python/util/install/ProgressPage.java | 122 + installer/src/java/org/python/util/install/ReadmePage.java | 75 + installer/src/java/org/python/util/install/StandalonePackager.java | 184 + installer/src/java/org/python/util/install/StartScriptGenerator.java | 244 ++ installer/src/java/org/python/util/install/SuccessPage.java | 55 + installer/src/java/org/python/util/install/TextConstants.java | 148 + installer/src/java/org/python/util/install/TextConstants_de.java | 148 + installer/src/java/org/python/util/install/TextConstants_en.java | 5 + installer/src/java/org/python/util/install/TextKeys.java | 135 + installer/src/java/org/python/util/install/TypePage.java | 268 ++ installer/src/java/org/python/util/install/UnicodeSequences.java | 19 + installer/src/java/org/python/util/install/ValidationEvent.java | 14 + installer/src/java/org/python/util/install/ValidationException.java | 19 + installer/src/java/org/python/util/install/ValidationInformationException.java | 21 + installer/src/java/org/python/util/install/ValidationListener.java | 11 + installer/src/java/org/python/util/install/Wizard.java | 90 + installer/src/java/org/python/util/install/WizardEvent.java | 13 + installer/src/java/org/python/util/install/WizardHeader.java | 89 + installer/src/java/org/python/util/install/WizardListener.java | 13 + installer/src/java/org/python/util/install/driver/Autotest.java | 274 ++ installer/src/java/org/python/util/install/driver/ConsoleAutotest.java | 30 + installer/src/java/org/python/util/install/driver/ConsoleDriver.java | 58 + installer/src/java/org/python/util/install/driver/DriverException.java | 17 + installer/src/java/org/python/util/install/driver/GuiAutotest.java | 188 + installer/src/java/org/python/util/install/driver/InstallationDriver.java | 416 +++ installer/src/java/org/python/util/install/driver/NormalVerifier.java | 287 ++ installer/src/java/org/python/util/install/driver/SilentAutotest.java | 25 + installer/src/java/org/python/util/install/driver/StandaloneVerifier.java | 74 + installer/src/java/org/python/util/install/driver/Tunnel.java | 61 + installer/src/java/org/python/util/install/driver/Verifier.java | 17 + installer/src/java/org/python/util/install/driver/jython_test.bat.template | 73 + installer/src/java/org/python/util/install/driver/jython_test.template | 58 + installer/src/java/org/python/util/install/jython_small_c.png | Bin installer/test/java/org/AllTests.java | 101 + installer/test/java/org/apache/commons/cli/ApplicationTest.java | 120 + installer/test/java/org/apache/commons/cli/BugsTest.java | 348 +++ installer/test/java/org/apache/commons/cli/BuildTest.java | 96 + installer/test/java/org/apache/commons/cli/GnuParseTest.java | 265 ++ installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java | 106 + installer/test/java/org/apache/commons/cli/HelpFormatterTest.java | 82 + installer/test/java/org/apache/commons/cli/OptionBuilderTest.java | 160 + installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java | 43 + installer/test/java/org/apache/commons/cli/OptionGroupTest.java | 246 ++ installer/test/java/org/apache/commons/cli/OptionsTest.java | 28 + installer/test/java/org/apache/commons/cli/ParseRequiredTest.java | 113 + installer/test/java/org/apache/commons/cli/ParseTest.java | 285 ++ installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java | 82 + installer/test/java/org/apache/commons/cli/PosixParserTest.java | 138 + installer/test/java/org/apache/commons/cli/TestHelpFormatter.java | 170 + installer/test/java/org/apache/commons/cli/ValueTest.java | 271 ++ installer/test/java/org/apache/commons/cli/ValuesTest.java | 248 ++ installer/test/java/org/python/util/install/ChildProcessExample.java | 32 + installer/test/java/org/python/util/install/ChildProcessTest.java | 80 + installer/test/java/org/python/util/install/ChmodTest_Standalone.java | 57 + installer/test/java/org/python/util/install/FileHelperTest.java | 271 ++ installer/test/java/org/python/util/install/FrameInstallerTest.java | 88 + installer/test/java/org/python/util/install/InstallationTest.java | 107 + installer/test/java/org/python/util/install/InstallationTypeTest.java | 119 + installer/test/java/org/python/util/install/InstallerCommandLineTest.java | 637 +++++ installer/test/java/org/python/util/install/JavaHomeHandlerTest.java | 114 + installer/test/java/org/python/util/install/JavaTest_Standalone.java | 32 + installer/test/java/org/python/util/install/StandalonePackagerTest.java | 183 + installer/test/java/org/python/util/install/StartScriptGeneratorTest.java | 277 ++ installer/test/java/org/python/util/install/UnicodeSequencesTest.java | 34 + installer/test/java/org/python/util/install/driver/AutotestTest.java | 78 + installer/test/java/org/python/util/install/driver/DrivableConsole.java | 82 + installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java | 37 + installer/test/java/org/python/util/install/driver/NormalVerifierTest.java | 131 + installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java | 72 + lib-python/2.7/BaseHTTPServer.py | 6 +- lib-python/2.7/CGIHTTPServer.py | 61 +- lib-python/2.7/Cookie.py | 4 +- lib-python/2.7/HTMLParser.py | 6 +- lib-python/2.7/SocketServer.py | 22 +- lib-python/2.7/StringIO.py | 2 +- lib-python/2.7/_LWPCookieJar.py | 4 +- lib-python/2.7/__future__.py | 2 +- lib-python/2.7/_osx_support.py | 488 ++++ lib-python/2.7/_pyio.py | 20 +- lib-python/2.7/_strptime.py | 15 +- lib-python/2.7/aifc.py | 38 +- lib-python/2.7/argparse.py | 44 +- lib-python/2.7/asyncore.py | 20 +- lib-python/2.7/bdb.py | 15 +- lib-python/2.7/calendar.py | 7 +- lib-python/2.7/cgi.py | 1 - lib-python/2.7/cgitb.py | 11 +- lib-python/2.7/cmd.py | 1 + lib-python/2.7/collections.py | 187 +- lib-python/2.7/compiler/consts.py | 2 +- lib-python/2.7/compiler/pycodegen.py | 4 +- lib-python/2.7/compiler/symbols.py | 4 +- lib-python/2.7/ctypes/test/test_bitfields.py | 20 + lib-python/2.7/ctypes/test/test_numbers.py | 10 + lib-python/2.7/ctypes/test/test_returnfuncptrs.py | 30 + lib-python/2.7/ctypes/test/test_structures.py | 9 + lib-python/2.7/ctypes/test/test_win32.py | 5 +- lib-python/2.7/ctypes/util.py | 29 + lib-python/2.7/curses/__init__.py | 2 +- lib-python/2.7/decimal.py | 8 +- lib-python/2.7/distutils/__init__.py | 2 +- lib-python/2.7/distutils/ccompiler.py | 2 + lib-python/2.7/distutils/command/check.py | 3 + lib-python/2.7/distutils/config.py | 7 +- lib-python/2.7/distutils/dir_util.py | 4 + lib-python/2.7/distutils/sysconfig.py | 118 +- lib-python/2.7/distutils/tests/test_build_ext.py | 5 +- lib-python/2.7/distutils/tests/test_dir_util.py | 18 + lib-python/2.7/distutils/tests/test_msvc9compiler.py | 2 +- lib-python/2.7/distutils/tests/test_register.py | 33 +- lib-python/2.7/distutils/tests/test_sdist.py | 7 +- lib-python/2.7/distutils/tests/test_sysconfig.py | 29 + lib-python/2.7/distutils/unixccompiler.py | 70 +- lib-python/2.7/distutils/util.py | 96 +- lib-python/2.7/doctest.py | 17 +- lib-python/2.7/email/_parseaddr.py | 8 +- lib-python/2.7/email/base64mime.py | 2 +- lib-python/2.7/email/feedparser.py | 4 +- lib-python/2.7/email/generator.py | 12 +- lib-python/2.7/email/test/test_email.py | 29 + lib-python/2.7/email/test/test_email_renamed.py | 32 + lib-python/2.7/email/utils.py | 4 +- lib-python/2.7/ftplib.py | 11 +- lib-python/2.7/glob.py | 25 +- lib-python/2.7/gzip.py | 81 +- lib-python/2.7/hashlib.py | 2 +- lib-python/2.7/heapq.py | 84 +- lib-python/2.7/httplib.py | 25 +- lib-python/2.7/idlelib/CallTips.py | 33 +- lib-python/2.7/idlelib/ColorDelegator.py | 9 +- lib-python/2.7/idlelib/EditorWindow.py | 56 +- lib-python/2.7/idlelib/FormatParagraph.py | 3 +- lib-python/2.7/idlelib/HyperParser.py | 5 + lib-python/2.7/idlelib/IOBinding.py | 37 +- lib-python/2.7/idlelib/NEWS.txt | 35 +- lib-python/2.7/idlelib/OutputWindow.py | 6 +- lib-python/2.7/idlelib/PyShell.py | 130 +- lib-python/2.7/idlelib/ReplaceDialog.py | 30 +- lib-python/2.7/idlelib/config-extensions.def | 2 + lib-python/2.7/idlelib/configDialog.py | 18 +- lib-python/2.7/idlelib/configHandler.py | 53 +- lib-python/2.7/idlelib/help.txt | 24 +- lib-python/2.7/idlelib/idlever.py | 2 +- lib-python/2.7/idlelib/macosxSupport.py | 16 +- lib-python/2.7/idlelib/run.py | 22 +- lib-python/2.7/io.py | 11 +- lib-python/2.7/json/__init__.py | 50 +- lib-python/2.7/json/decoder.py | 17 +- lib-python/2.7/json/encoder.py | 17 +- lib-python/2.7/json/tests/test_decode.py | 9 + lib-python/2.7/json/tests/test_dump.py | 9 + lib-python/2.7/json/tests/test_fail.py | 24 +- lib-python/2.7/json/tests/test_float.py | 15 + lib-python/2.7/json/tests/test_pass1.py | 20 +- lib-python/2.7/json/tests/test_tool.py | 69 + lib-python/2.7/json/tool.py | 17 +- lib-python/2.7/lib-tk/Tkinter.py | 74 +- lib-python/2.7/lib-tk/test/test_ttk/test_functions.py | 40 +- lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py | 8 + lib-python/2.7/lib-tk/tkSimpleDialog.py | 2 +- lib-python/2.7/lib-tk/ttk.py | 126 +- lib-python/2.7/lib2to3/fixer_util.py | 16 +- lib-python/2.7/lib2to3/pgen2/driver.py | 17 + lib-python/2.7/lib2to3/refactor.py | 2 +- lib-python/2.7/lib2to3/tests/test_fixers.py | 12 + lib-python/2.7/locale.py | 10 +- lib-python/2.7/logging/__init__.py | 17 +- lib-python/2.7/logging/handlers.py | 72 +- lib-python/2.7/mailbox.py | 76 +- lib-python/2.7/mimetypes.py | 3 +- lib-python/2.7/multiprocessing/connection.py | 11 +- lib-python/2.7/multiprocessing/dummy/__init__.py | 3 +- lib-python/2.7/multiprocessing/forking.py | 20 +- lib-python/2.7/multiprocessing/pool.py | 35 +- lib-python/2.7/multiprocessing/process.py | 6 +- lib-python/2.7/multiprocessing/util.py | 39 +- lib-python/2.7/plat-generic/regen | 2 +- lib-python/2.7/platform.py | 37 +- lib-python/2.7/posixpath.py | 96 +- lib-python/2.7/pstats.py | 10 +- lib-python/2.7/py_compile.py | 2 +- lib-python/2.7/pyclbr.py | 2 + lib-python/2.7/pydoc.py | 7 +- lib-python/2.7/random.py | 20 +- lib-python/2.7/rfc822.py | 2 +- lib-python/2.7/rlcompleter.py | 36 +- lib-python/2.7/runpy.py | 2 +- lib-python/2.7/shutil.py | 8 +- lib-python/2.7/smtplib.py | 4 +- lib-python/2.7/socket.py | 4 +- lib-python/2.7/sqlite3/dbapi2.py | 4 +- lib-python/2.7/sqlite3/dump.py | 9 +- lib-python/2.7/sqlite3/test/dump.py | 23 + lib-python/2.7/sqlite3/test/hooks.py | 19 + lib-python/2.7/sqlite3/test/regression.py | 28 +- lib-python/2.7/sqlite3/test/userfunctions.py | 60 +- lib-python/2.7/sre_compile.py | 1 + lib-python/2.7/sre_constants.py | 4 +- lib-python/2.7/sre_parse.py | 19 +- lib-python/2.7/ssl.py | 20 +- lib-python/2.7/string.py | 8 +- lib-python/2.7/subprocess.py | 55 +- lib-python/2.7/sysconfig.py | 158 +- lib-python/2.7/tarfile.py | 16 +- lib-python/2.7/telnetlib.py | 130 + lib-python/2.7/tempfile.py | 43 +- lib-python/2.7/test/crashers/buffer_mutate.py | 30 + lib-python/2.7/test/keycert.pem | 59 +- lib-python/2.7/test/mp_fork_bomb.py | 16 + lib-python/2.7/test/pickletester.py | 35 +- lib-python/2.7/test/regrtest.py | 8 +- lib-python/2.7/test/sample_doctest_no_docstrings.py | 12 + lib-python/2.7/test/sample_doctest_no_doctests.py | 15 + lib-python/2.7/test/script_helper.py | 8 +- lib-python/2.7/test/sha256.pem | 233 +- lib-python/2.7/test/string_tests.py | 18 + lib-python/2.7/test/subprocessdata/sigchild_ignore.py | 11 +- lib-python/2.7/test/test_StringIO.py | 42 + lib-python/2.7/test/test__osx_support.py | 279 ++ lib-python/2.7/test/test_aifc.py | 7 + lib-python/2.7/test/test_argparse.py | 137 +- lib-python/2.7/test/test_array.py | 13 + lib-python/2.7/test/test_ast.py | 6 + lib-python/2.7/test/test_asyncore.py | 27 +- lib-python/2.7/test/test_audioop.py | 405 ++- lib-python/2.7/test/test_bigmem.py | 8 +- lib-python/2.7/test/test_bisect.py | 53 +- lib-python/2.7/test/test_builtin.py | 6 +- lib-python/2.7/test/test_bytes.py | 21 + lib-python/2.7/test/test_bz2.py | 81 +- lib-python/2.7/test/test_calendar.py | 26 +- lib-python/2.7/test/test_capi.py | 56 +- lib-python/2.7/test/test_cmd.py | 8 +- lib-python/2.7/test/test_cmd_line.py | 38 +- lib-python/2.7/test/test_cmd_line_script.py | 18 +- lib-python/2.7/test/test_codeccallbacks.py | 6 +- lib-python/2.7/test/test_codecs.py | 410 +++- lib-python/2.7/test/test_codeop.py | 2 +- lib-python/2.7/test/test_compile.py | 28 + lib-python/2.7/test/test_cookie.py | 15 +- lib-python/2.7/test/test_cpickle.py | 17 +- lib-python/2.7/test/test_csv.py | 9 + lib-python/2.7/test/test_decimal.py | 12 + lib-python/2.7/test/test_deque.py | 16 + lib-python/2.7/test/test_descr.py | 26 +- lib-python/2.7/test/test_dict.py | 8 + lib-python/2.7/test/test_dictcomps.py | 117 +- lib-python/2.7/test/test_doctest.py | 29 +- lib-python/2.7/test/test_docxmlrpc.py | 2 +- lib-python/2.7/test/test_email.py | 2 + lib-python/2.7/test/test_exceptions.py | 12 + lib-python/2.7/test/test_fcntl.py | 21 + lib-python/2.7/test/test_file2k.py | 147 +- lib-python/2.7/test/test_file_eintr.py | 239 ++ lib-python/2.7/test/test_fileio.py | 49 +- lib-python/2.7/test/test_format.py | 10 + lib-python/2.7/test/test_functools.py | 21 +- lib-python/2.7/test/test_gc.py | 69 + lib-python/2.7/test/test_gdb.py | 75 +- lib-python/2.7/test/test_generators.py | 3 +- lib-python/2.7/test/test_genexps.py | 3 +- lib-python/2.7/test/test_glob.py | 115 +- lib-python/2.7/test/test_gzip.py | 25 + lib-python/2.7/test/test_hashlib.py | 50 +- lib-python/2.7/test/test_heapq.py | 26 + lib-python/2.7/test/test_htmlparser.py | 10 + lib-python/2.7/test/test_httplib.py | 69 +- lib-python/2.7/test/test_httpservers.py | 76 +- lib-python/2.7/test/test_imaplib.py | 2 +- lib-python/2.7/test/test_import.py | 103 +- lib-python/2.7/test/test_int.py | 63 +- lib-python/2.7/test/test_io.py | 165 +- lib-python/2.7/test/test_iter.py | 15 + lib-python/2.7/test/test_itertools.py | 6 + lib-python/2.7/test/test_logging.py | 49 +- lib-python/2.7/test/test_long.py | 6 +- lib-python/2.7/test/test_mailbox.py | 208 +- lib-python/2.7/test/test_marshal.py | 51 +- lib-python/2.7/test/test_memoryio.py | 16 +- lib-python/2.7/test/test_minidom.py | 2 +- lib-python/2.7/test/test_mmap.py | 20 + lib-python/2.7/test/test_multiprocessing.py | 173 +- lib-python/2.7/test/test_mutex.py | 2 +- lib-python/2.7/test/test_old_mailbox.py | 16 +- lib-python/2.7/test/test_optparse.py | 7 + lib-python/2.7/test/test_os.py | 16 +- lib-python/2.7/test/test_parser.py | 61 +- lib-python/2.7/test/test_pdb.py | 61 +- lib-python/2.7/test/test_peepholer.py | 13 +- lib-python/2.7/test/test_pickle.py | 20 +- lib-python/2.7/test/test_poll.py | 10 + lib-python/2.7/test/test_posix.py | 133 +- lib-python/2.7/test/test_posixpath.py | 103 +- lib-python/2.7/test/test_property.py | 4 +- lib-python/2.7/test/test_pty.py | 2 +- lib-python/2.7/test/test_pwd.py | 9 + lib-python/2.7/test/test_pyclbr.py | 5 + lib-python/2.7/test/test_pydoc.py | 43 +- lib-python/2.7/test/test_pyexpat.py | 55 +- lib-python/2.7/test/test_random.py | 56 +- lib-python/2.7/test/test_re.py | 94 + lib-python/2.7/test/test_readline.py | 4 + lib-python/2.7/test/test_resource.py | 17 + lib-python/2.7/test/test_sax.py | 166 +- lib-python/2.7/test/test_select.py | 9 + lib-python/2.7/test/test_shutil.py | 30 + lib-python/2.7/test/test_signal.py | 16 +- lib-python/2.7/test/test_socket.py | 111 +- lib-python/2.7/test/test_socketserver.py | 41 +- lib-python/2.7/test/test_ssl.py | 50 +- lib-python/2.7/test/test_str.py | 27 + lib-python/2.7/test/test_strptime.py | 8 + lib-python/2.7/test/test_struct.py | 26 +- lib-python/2.7/test/test_subprocess.py | 82 + lib-python/2.7/test/test_support.py | 152 +- lib-python/2.7/test/test_sys.py | 207 +- lib-python/2.7/test/test_sys_settrace.py | 13 +- lib-python/2.7/test/test_sysconfig.py | 9 + lib-python/2.7/test/test_tarfile.py | 32 +- lib-python/2.7/test/test_tcl.py | 22 + lib-python/2.7/test/test_telnetlib.py | 92 +- lib-python/2.7/test/test_tempfile.py | 87 +- lib-python/2.7/test/test_textwrap.py | 62 +- lib-python/2.7/test/test_thread.py | 23 + lib-python/2.7/test/test_threading.py | 29 + lib-python/2.7/test/test_time.py | 2 +- lib-python/2.7/test/test_tokenize.py | 29 + lib-python/2.7/test/test_tools.py | 339 +++- lib-python/2.7/test/test_ucn.py | 23 + lib-python/2.7/test/test_unicode.py | 27 + lib-python/2.7/test/test_urllib.py | 21 + lib-python/2.7/test/test_urllib2.py | 58 +- lib-python/2.7/test/test_urllib2_localnet.py | 8 + lib-python/2.7/test/test_urllib2net.py | 4 +- lib-python/2.7/test/test_urlparse.py | 49 + lib-python/2.7/test/test_uu.py | 4 +- lib-python/2.7/test/test_weakref.py | 101 +- lib-python/2.7/test/test_winreg.py | 45 +- lib-python/2.7/test/test_winsound.py | 1 + lib-python/2.7/test/test_wsgiref.py | 108 +- lib-python/2.7/test/test_xml_etree.py | 20 + lib-python/2.7/test/test_xrange.py | 75 + lib-python/2.7/test/test_zipfile.py | 141 +- lib-python/2.7/test/test_zipimport_support.py | 26 +- lib-python/2.7/test/test_zlib.py | 37 + lib-python/2.7/test/testbz2_bigmem.bz2 | Bin lib-python/2.7/test/testtar.tar | Bin lib-python/2.7/textwrap.py | 12 +- lib-python/2.7/threading.py | 7 +- lib-python/2.7/tokenize.py | 14 +- lib-python/2.7/traceback.py | 2 +- lib-python/2.7/unittest/case.py | 17 +- lib-python/2.7/unittest/main.py | 5 +- lib-python/2.7/unittest/runner.py | 2 +- lib-python/2.7/unittest/signals.py | 16 +- lib-python/2.7/unittest/test/test_break.py | 32 + lib-python/2.7/unittest/test/test_discovery.py | 14 + lib-python/2.7/unittest/test/test_runner.py | 13 + lib-python/2.7/unittest/test/test_skipping.py | 30 + lib-python/2.7/urllib2.py | 11 +- lib-python/2.7/urlparse.py | 33 +- lib-python/2.7/wave.py | 12 +- lib-python/2.7/wsgiref/handlers.py | 12 +- lib-python/2.7/wsgiref/validate.py | 4 +- lib-python/2.7/xml/dom/minidom.py | 5 +- lib-python/2.7/xml/etree/ElementTree.py | 7 +- lib-python/2.7/xml/sax/_exceptions.py | 6 +- lib-python/2.7/xml/sax/expatreader.py | 5 +- lib-python/2.7/xml/sax/saxutils.py | 114 +- lib-python/2.7/xml/sax/xmlreader.py | 2 +- lib-python/2.7/xmlrpclib.py | 2 +- lib-python/2.7/zipfile.py | 551 ++-- maven/build.xml | 6 +- src/org/python/core/BaseBytes.java | 402 ++- src/org/python/core/BuiltinDocs.java | 4 +- src/org/python/core/PyBUF.java | 52 +- src/org/python/core/PyBuffer.java | 43 +- src/org/python/core/PyByteArray.java | 430 ++- src/org/python/core/PyDictionary.java | 23 +- src/org/python/core/PyString.java | 21 +- src/org/python/core/PySystemState.java | 14 + src/org/python/core/PyXRange.java | 3 +- src/org/python/core/buffer/BaseBuffer.java | 9 +- src/org/python/core/buffer/SimpleBuffer.java | 2 + src/org/python/core/buffer/SimpleStringBuffer.java | 10 +- src/org/python/core/buffer/Strided1DBuffer.java | 25 +- src/org/python/core/buffer/Strided1DWritableBuffer.java | 2 +- src/org/python/core/codecs.java | 10 +- src/org/python/core/io/StreamIO.java | 58 +- src/org/python/modules/SHA224Digest.java | 12 +- src/org/python/modules/_codecs.java | 8 +- src/org/python/modules/_csv/PyReader.java | 12 +- src/org/python/modules/_csv/PyWriter.java | 12 +- src/org/python/modules/_io/OpenMode.java | 9 +- src/org/python/modules/_io/PyFileIO.java | 69 +- src/org/python/modules/_io/PyIOBase.java | 8 +- src/org/python/modules/_io/PyRawIOBase.java | 2 +- src/org/python/modules/_sre.java | 3 + src/org/python/modules/_threading/Condition.java | 14 +- src/org/python/modules/time/Time.java | 5 + src/org/python/util/ProxyCompiler.java | 15 +- tests/java/org/python/modules/_io/_ioTest.java | 120 +- 484 files changed, 30183 insertions(+), 5061 deletions(-) diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -63,3 +63,5 @@ 4f5e3c12edc040058d724d4469a53e8649e64420 v2.7.0a1 ac5609677c1382f14d0e04178fe6dd4108e4c231 v2.7.0a2 3d2dbae23c5292b7f31ac4c92fa6d1afd8bd8eb2 v2.5.3 +f5b12dc4ff970c9594c99fc895772958c4a669a0 v2.5.4rc1 +8a556c4cc2810912e4ef277d53668ffc9d9f5772 v2.7b1 diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -93,11 +93,15 @@ Costantino Cerbo Alex Groenholm Anselm Kruis + Andreas St??hrk Dmitry Jemerov Miki Tebeka Jeff Allen Julian Kennedy Arfrever Frehtes Taifersar Arahesis + Andreas St??hrk + Christian Klein + Jezreel Ng Local Variables: mode: indented-text diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -275,10 +275,48 @@ self._buffer = buf self._pos = 0 + # Jython: modelled after bytesio.c::bytesio_getstate def __getstate__(self): - if self.closed: - raise ValueError("__getstate__ on closed file") - return self.__dict__.copy() + d = getattr(self, '__dict__', None) + if d is not None : + d = d.copy() + return (self.getvalue(), self._pos, d) + + # Jython: modelled after bytesio.c::bytesio_setstate + def __setstate__(self, state): + + if not isinstance(state, tuple) or len(state) < 3 : + fmt = "%s.__setstate__ argument should be 3-tuple got %s" + raise TypeError( fmt % (type(self), type(state)) ) + + # Reset the object to its default state. This is only needed to handle + # the case of repeated calls to __setstate__. */ + self._buffer = bytearray() + self._pos = 0 + + # Set the value of the internal buffer. If state[0] does not support the + # buffer protocol, bytesio_write will raise the appropriate TypeError. */ + self.write(state[0]); + + # Carefully set the position value. Alternatively, we could use the seek + # method instead of modifying self._pos directly to better protect the + # object internal state against erroneous (or malicious) inputs. */ + p = state[1] + if not isinstance(p, (int, long)) : + fmt = "second item of state must be an integer, got %s" + raise TypeError( fmt % type(p) ) + elif p < 0 : + raise ValueError("position value cannot be negative") + self._pos = p + + # Set the dictionary of the instance variables. */ + d = state[2] + if not d is None : + if isinstance(d, dict) : + self.__dict__ = d + else : + fmt = "third item of state should be a dict, got %s" + raise TypeError( fmt % type(d) ) def getvalue(self): """Return the bytes value (contents) of the buffer @@ -367,12 +405,15 @@ return pos def readable(self): + self._checkClosed() return True def writable(self): + self._checkClosed() return True def seekable(self): + self._checkClosed() return True @@ -1042,31 +1083,30 @@ def seekable(self): self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation + self._checkClosed() return self._seekable def readable(self): self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation return self.buffer.readable() def writable(self): self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation return self.buffer.writable() def flush(self): - self._checkInitialized() # Jython: to forbid use in an invalid state - self._checkClosed() # Jython: compatibility with C implementation + self._checkInitialized() # Jython: to forbid use in an invalid state self.buffer.flush() self._telling = self._seekable def close(self): if self.buffer is not None and not self.closed: - # Jython difference: flush and close via super. - # Sets __closed for quick _checkClosed(). - super(TextIOWrapper, self).close() - self.buffer.close() + try: + # Jython difference: flush and close via super. + # Sets __closed for quick _checkClosed(). + super(TextIOWrapper, self).close() + finally: + self.buffer.close() # Jython difference: @property closed(self) inherited from _IOBase.__closed @@ -1483,6 +1523,11 @@ """ def __init__(self, initial_value="", newline="\n"): + + # Newline mark needs to be in bytes: convert if not already so + if isinstance(newline, unicode) : + newline = newline.encode("utf-8") + super(StringIO, self).__init__(BytesIO(), encoding="utf-8", errors="strict", @@ -1491,11 +1536,64 @@ # C version, even under Windows. if newline is None: self._writetranslate = False - if initial_value: - if not isinstance(initial_value, unicode): - initial_value = unicode(initial_value) - self.write(initial_value) - self.seek(0) + # An initial value may have been supplied (and must be unicode) + if initial_value is not None: + if not isinstance(initial_value, unicode) : + fmt = "initial value should be unicode or None, got %s" + raise TypeError( fmt % type(initial_value) ) + if initial_value: + self.write(initial_value) + self.seek(0) + + # Jython: modelled after stringio.c::stringio_getstate + def __getstate__(self): + d = getattr(self, '__dict__', None) + if d is not None : + d = d.copy() + return (self.getvalue(), self._readnl, self.tell(), d) + + # Jython: modelled after stringio.c:stringio_setstate + def __setstate__(self, state): + self._checkClosed() + + if not isinstance(state, tuple) or len(state) < 4 : + fmt = "%s.__setstate__ argument should be 4-tuple got %s" + raise TypeError( fmt % (type(self), type(state)) ) + + # Initialize the object's state, but empty + self.__init__(None, state[1]) + + # Write the buffer, bypassing end-of-line translation. + value = state[0] + if value is not None: + if not isinstance(value, unicode) : + fmt = "ivalue should be unicode or None, got %s" + raise TypeError( fmt % type(value) ) + encoder = self._encoder or self._get_encoder() + b = encoder.encode(state[0]) + self.buffer.write(b) + + # Reset the object to its default state. This is only needed to handle + # the case of repeated calls to __setstate__. + self.seek(0) + + # Set the position value using seek. A long is tolerated (e.g from pickle). + p = state[2] + if not isinstance(p, (int, long)) : + fmt = "third item of state must be an integer, got %s" + raise TypeError( fmt % type(p) ) + elif p < 0 : + raise ValueError("position value cannot be negative") + self.seek(p) + + # Set the dictionary of the instance variables. */ + d = state[3] + if not d is None : + if isinstance(d, dict) : + self.__dict__ = d + else : + fmt = "fourth item of state should be a dict, got %s" + raise TypeError( fmt % type(d) ) def getvalue(self): self.flush() diff --git a/Lib/_warnings.py b/Lib/_warnings.py deleted file mode 100644 --- a/Lib/_warnings.py +++ /dev/null @@ -1,388 +0,0 @@ -""" -XXX: faked out Java part of the warnings subsystem, mostly copied from - warnings.py. TODO: rewrite in Java! -""" - -# Note: function level imports should *not* be used -# in this module as it may cause import lock deadlock. -# See bug 683658. -import linecache -import sys -import types - -__all__ = ["filters", "default_action", "once_registry", "warn", - "warn_explicit"] - -onceregistry = once_registry = {} -defaultaction = default_action = "default" - -def warnpy3k(message, category=None, stacklevel=1): - """Issue a deprecation warning for Python 3.x related changes. - - Warnings are omitted unless Python is started with the -3 option. - """ - if sys.py3kwarning: - if category is None: - category = DeprecationWarning - warn(message, category, stacklevel+1) - -def _show_warning(message, category, filename, lineno, file=None, line=None): - """Hook to write a warning to a file; replace if you like.""" - if file is None: - file = sys.stderr - try: - file.write(formatwarning(message, category, filename, lineno, line)) - except IOError: - pass # the file (probably stderr) is invalid - this warning gets lost. -# Keep a working version around in case the deprecation of the old API is -# triggered. -showwarning = _show_warning - -def formatwarning(message, category, filename, lineno, line=None): - """Function to format a warning the standard way.""" - s = "%s:%s: %s: %s\n" % (filename, lineno, category.__name__, message) - line = linecache.getline(filename, lineno) if line is None else line - if line: - line = line.strip() - s += " %s\n" % line - return s - -def filterwarnings(action, message="", category=Warning, module="", lineno=0, - append=0): - """Insert an entry into the list of warnings filters (at the front). - - 'action' -- one of "error", "ignore", "always", "default", "module", - or "once" - 'message' -- a regex that the warning message must match - 'category' -- a class that the warning must be a subclass of - 'module' -- a regex that the module name must match - 'lineno' -- an integer line number, 0 matches all warnings - 'append' -- if true, append to the list of filters - """ - import re - assert action in ("error", "ignore", "always", "default", "module", - "once"), "invalid action: %r" % (action,) - assert isinstance(message, basestring), "message must be a string" - assert isinstance(category, (type, types.ClassType)), \ - "category must be a class" - assert issubclass(category, Warning), "category must be a Warning subclass" - assert isinstance(module, basestring), "module must be a string" - assert isinstance(lineno, int) and lineno >= 0, \ - "lineno must be an int >= 0" - item = (action, re.compile(message, re.I), category, - re.compile(module), lineno) - if append: - filters.append(item) - else: - filters.insert(0, item) - -def simplefilter(action, category=Warning, lineno=0, append=0): - """Insert a simple entry into the list of warnings filters (at the front). - - A simple filter matches all modules and messages. - 'action' -- one of "error", "ignore", "always", "default", "module", - or "once" - 'category' -- a class that the warning must be a subclass of - 'lineno' -- an integer line number, 0 matches all warnings - 'append' -- if true, append to the list of filters - """ - assert action in ("error", "ignore", "always", "default", "module", - "once"), "invalid action: %r" % (action,) - assert isinstance(lineno, int) and lineno >= 0, \ - "lineno must be an int >= 0" - item = (action, None, category, None, lineno) - if append: - filters.append(item) - else: - filters.insert(0, item) - -def resetwarnings(): - """Clear the list of warning filters, so that no filters are active.""" - filters[:] = [] - -class _OptionError(Exception): - """Exception used by option processing helpers.""" - pass - -# Helper to process -W options passed via sys.warnoptions -def _processoptions(args): - for arg in args: - try: - _setoption(arg) - except _OptionError, msg: - print >>sys.stderr, "Invalid -W option ignored:", msg - -# Helper for _processoptions() -def _setoption(arg): - import re - parts = arg.split(':') - if len(parts) > 5: - raise _OptionError("too many fields (max 5): %r" % (arg,)) - while len(parts) < 5: - parts.append('') - action, message, category, module, lineno = [s.strip() - for s in parts] - action = _getaction(action) - message = re.escape(message) - category = _getcategory(category) - module = re.escape(module) - if module: - module = module + '$' - if lineno: - try: - lineno = int(lineno) - if lineno < 0: - raise ValueError - except (ValueError, OverflowError): - raise _OptionError("invalid lineno %r" % (lineno,)) - else: - lineno = 0 - filterwarnings(action, message, category, module, lineno) - -# Helper for _setoption() -def _getaction(action): - if not action: - return "default" - if action == "all": return "always" # Alias - for a in ('default', 'always', 'ignore', 'module', 'once', 'error'): - if a.startswith(action): - return a - raise _OptionError("invalid action: %r" % (action,)) - -# Helper for _setoption() -def _getcategory(category): - import re - if not category: - return Warning - if re.match("^[a-zA-Z0-9_]+$", category): - try: - cat = eval(category) - except NameError: - raise _OptionError("unknown warning category: %r" % (category,)) - else: - i = category.rfind(".") - module = category[:i] - klass = category[i+1:] - try: - m = __import__(module, None, None, [klass]) - except ImportError: - raise _OptionError("invalid module name: %r" % (module,)) - try: - cat = getattr(m, klass) - except AttributeError: - raise _OptionError("unknown warning category: %r" % (category,)) - if not issubclass(cat, Warning): - raise _OptionError("invalid warning category: %r" % (category,)) - return cat - -class SysGlobals: - '''sys.__dict__ values are reflectedfields, so we use this.''' - def __getitem__(self, key): - try: - return getattr(sys, key) - except AttributeError: - raise KeyError(key) - - def get(self, key, default=None): - if key in self: - return self[key] - return default - - def setdefault(self, key, default=None): - if key not in self: - sys.__dict__[key] = default - return self[key] - - def __contains__(self, key): - return key in sys.__dict__ - -# Code typically replaced by _warnings -def warn(message, category=None, stacklevel=1): - """Issue a warning, or maybe ignore it or raise an exception.""" - # Check if message is already a Warning object - if isinstance(message, Warning): - category = message.__class__ - # Check category argument - if category is None: - category = UserWarning - assert issubclass(category, Warning) - # Get context information - try: - caller = sys._getframe(stacklevel) - except ValueError: - globals = SysGlobals() - lineno = 1 - else: - globals = caller.f_globals - lineno = caller.f_lineno - if '__name__' in globals: - module = globals['__name__'] - else: - module = "" - filename = globals.get('__file__') - if filename: - fnl = filename.lower() - if fnl.endswith((".pyc", ".pyo")): - filename = filename[:-1] - elif fnl.endswith("$py.class"): - filename = filename[:-9] + '.py' - else: - if module == "__main__": - try: - filename = sys.argv[0] - except (AttributeError, TypeError): - # embedded interpreters don't have sys.argv, see bug #839151 - filename = '__main__' - if not filename: - filename = module - registry = globals.setdefault("__warningregistry__", {}) - warn_explicit(message, category, filename, lineno, module, registry, - globals) - -def warn_explicit(message, category, filename, lineno, - module=None, registry=None, module_globals=None): - lineno = int(lineno) - if module is None: - module = filename or "" - if module[-3:].lower() == ".py": - module = module[:-3] # XXX What about leading pathname? - if registry is None: - registry = {} - if isinstance(message, Warning): - text = str(message) - category = message.__class__ - else: - text = message - message = category(message) - key = (text, category, lineno) - # Quick test for common case - if registry.get(key): - return - # Search the filters - for item in filters: - action, msg, cat, mod, ln = item - if ((msg is None or msg.match(text)) and - issubclass(category, cat) and - (mod is None or mod.match(module)) and - (ln == 0 or lineno == ln)): - break - else: - action = defaultaction - # Early exit actions - if action == "ignore": - registry[key] = 1 - return - - # Prime the linecache for formatting, in case the - # "file" is actually in a zipfile or something. - linecache.getlines(filename, module_globals) - - if action == "error": - raise message - # Other actions - if action == "once": - _onceregistry = globals().get('onceregistry', once_registry) - registry[key] = 1 - oncekey = (text, category) - if _onceregistry.get(oncekey): - return - _onceregistry[oncekey] = 1 - elif action == "always": - pass - elif action == "module": - registry[key] = 1 - altkey = (text, category, 0) - if registry.get(altkey): - return - registry[altkey] = 1 - elif action == "default": - registry[key] = 1 - else: - # Unrecognized actions are errors - raise RuntimeError( - "Unrecognized action (%r) in warnings.filters:\n %s" % - (action, item)) - # Print message and context - fn = globals().get('showwarning', _show_warning) - fn(message, category, filename. lineno) - - -class WarningMessage(object): - - """Holds the result of a single showwarning() call.""" - - _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", - "line") - - def __init__(self, message, category, filename, lineno, file=None, - line=None): - local_values = locals() - for attr in self._WARNING_DETAILS: - setattr(self, attr, local_values[attr]) - self._category_name = category.__name__ if category else None - - def __str__(self): - return ("{message : %r, category : %r, filename : %r, lineno : %s, " - "line : %r}" % (self.message, self._category_name, - self.filename, self.lineno, self.line)) - - -class catch_warnings(object): - - """A context manager that copies and restores the warnings filter upon - exiting the context. - - The 'record' argument specifies whether warnings should be captured by a - custom implementation of warnings.showwarning() and be appended to a list - returned by the context manager. Otherwise None is returned by the context - manager. The objects appended to the list are arguments whose attributes - mirror the arguments to showwarning(). - - The 'module' argument is to specify an alternative module to the module - named 'warnings' and imported under that name. This argument is only useful - when testing the warnings module itself. - - """ - - def __init__(self, record=False, module=None): - """Specify whether to record warnings and if an alternative module - should be used other than sys.modules['warnings']. - - For compatibility with Python 3.0, please consider all arguments to be - keyword-only. - - """ - self._record = record - self._module = sys.modules['warnings'] if module is None else module - self._entered = False - - def __repr__(self): - args = [] - if self._record: - args.append("record=True") - if self._module is not sys.modules['warnings']: - args.append("module=%r" % self._module) - name = type(self).__name__ - return "%s(%s)" % (name, ", ".join(args)) - - def __enter__(self): - if self._entered: - raise RuntimeError("Cannot enter %r twice" % self) - self._entered = True - self._filters = self._module.filters - self._module.filters = self._module._filters = self._filters[:] - self._showwarning = self._module.showwarning - if self._record: - log = [] - def showwarning(*args, **kwargs): - log.append(WarningMessage(*args, **kwargs)) - self._module.showwarning = showwarning - return log - else: - return None - - def __exit__(self, *exc_info): - if not self._entered: - raise RuntimeError("Cannot exit %r without entering first" % self) - self._module.filters = self._module._filters = self._filters - self._module.showwarning = self._showwarning diff --git a/Lib/decimal.py b/Lib/decimal.py --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -1580,7 +1580,13 @@ def __float__(self): """Float representation.""" - return float(str(self)) + if self._isnan(): + if self.is_snan(): + raise ValueError("Cannot convert signaling NaN to float") + s = "-nan" if self._sign else "nan" + else: + s = str(self) + return float(s) def __int__(self): """Converts self to an int, truncating if necessary.""" diff --git a/Lib/doctest.py b/Lib/doctest.py --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -2318,7 +2318,8 @@ return "Doctest: " + self._dt_test.name class SkipDocTestCase(DocTestCase): - def __init__(self): + def __init__(self, module): + self.module = module DocTestCase.__init__(self, None) def setUp(self): @@ -2328,7 +2329,10 @@ pass def shortDescription(self): - return "Skipping tests from %s" % module.__name__ + return "Skipping tests from %s" % self.module.__name__ + + __str__ = shortDescription + def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, **options): @@ -2376,12 +2380,17 @@ if not tests and sys.flags.optimize >=2: # Skip doctests when running with -O2 suite = unittest.TestSuite() - suite.addTest(SkipDocTestCase()) + suite.addTest(SkipDocTestCase(module)) return suite elif not tests: # Why do we want to do this? Because it reveals a bug that might # otherwise be hidden. - raise ValueError(module, "has no tests") + # It is probably a bug that this exception is not also raised if the + # number of doctest examples in tests is zero (i.e. if no doctest + # examples were found). However, we should probably not be raising + # an exception at all here, though it is too late to make this change + # for a maintenance release. See also issue #14649. + raise ValueError(module, "has no docstrings") tests.sort() suite = unittest.TestSuite() diff --git a/Lib/platform.py b/Lib/platform.py --- a/Lib/platform.py +++ b/Lib/platform.py @@ -34,6 +34,7 @@ # # # +# 1.0.7 - added DEV_NULL # 1.0.6 - added linux_distribution() # 1.0.5 - fixed Java support to allow running the module on Jython # 1.0.4 - added IronPython support @@ -110,14 +111,28 @@ """ -__version__ = '1.0.6' +__version__ = '1.0.7' import sys,string,os,re +### Globals & Constants if sys.platform.startswith("java"): from java.lang import System from org.python.core.Py import newString +# Determine the platform's /dev/null device +try: + DEV_NULL = os.devnull +except AttributeError: + # os.devnull was added in Python 2.4, so emulate it for earlier + # Python versions + if sys.platform in ('dos','win32','win16','os2'): + # Use the old CP/M NUL as device name + DEV_NULL = 'NUL' + else: + # Standard Unix uses /dev/null + DEV_NULL = '/dev/null' + ### Platform specific APIs _libc_search = re.compile(r'(__libc_init)' @@ -171,7 +186,7 @@ elif so: if lib != 'glibc': lib = 'libc' - if soversion > version: + if soversion and soversion > version: version = soversion if threads and version[-len(threads):] != threads: version = version + threads @@ -276,24 +291,6 @@ id = l[1] return '', version, id -def _test_parse_release_file(): - - for input, output in ( - # Examples of release file contents: - ('SuSE Linux 9.3 (x86-64)', ('SuSE Linux ', '9.3', 'x86-64')) - ('SUSE LINUX 10.1 (X86-64)', ('SUSE LINUX ', '10.1', 'X86-64')) - ('SUSE LINUX 10.1 (i586)', ('SUSE LINUX ', '10.1', 'i586')) - ('Fedora Core release 5 (Bordeaux)', ('Fedora Core', '5', 'Bordeaux')) - ('Red Hat Linux release 8.0 (Psyche)', ('Red Hat Linux', '8.0', 'Psyche')) - ('Red Hat Linux release 9 (Shrike)', ('Red Hat Linux', '9', 'Shrike')) - ('Red Hat Enterprise Linux release 4 (Nahant)', ('Red Hat Enterprise Linux', '4', 'Nahant')) - ('CentOS release 4', ('CentOS', '4', None)) - ('Rocks release 4.2.1 (Cydonia)', ('Rocks', '4.2.1', 'Cydonia')) - ): - parsed = _parse_release_file(input) - if parsed != output: - print (input, parsed) - def linux_distribution(distname='', version='', id='', supported_dists=_supported_dists, @@ -474,7 +471,16 @@ _ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) ' '.*' - 'Version ([\d.]+))') + '\[.* ([\d.]+)\])') + +# Examples of VER command output: +# +# Windows 2000: Microsoft Windows 2000 [Version 5.00.2195] +# Windows XP: Microsoft Windows XP [Version 5.1.2600] +# Windows Vista: Microsoft Windows [Version 6.0.6002] +# +# Note that the "Version" string gets localized on different +# Windows versions. def _syscmd_ver(system='', release='', version='', @@ -500,7 +506,7 @@ info = pipe.read() if pipe.close(): raise os.error,'command failed' - # XXX How can I supress shell errors from being written + # XXX How can I suppress shell errors from being written # to stderr ? except os.error,why: #print 'Command %s failed: %s' % (cmd,why) @@ -551,7 +557,7 @@ """ Get additional version information from the Windows Registry and return a tuple (version,csd,ptype) referring to version - number, CSD level and OS type (multi/single + number, CSD level (service pack), and OS type (multi/single processor). As a hint: ptype returns 'Uniprocessor Free' on single @@ -613,6 +619,7 @@ else: if csd[:13] == 'Service Pack ': csd = 'SP' + csd[13:] + if plat == VER_PLATFORM_WIN32_WINDOWS: regkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion' # Try to guess the release name @@ -627,6 +634,7 @@ release = 'postMe' elif maj == 5: release = '2000' + elif plat == VER_PLATFORM_WIN32_NT: regkey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' if maj <= 4: @@ -668,8 +676,14 @@ release = '7' else: release = '2008ServerR2' + elif min == 2: + if product_type == VER_NT_WORKSTATION: + release = '8' + else: + release = '2012Server' else: - release = 'post2008Server' + release = 'post2012Server' + else: if not release: # E.g. Win3.1 with win32s @@ -759,6 +773,7 @@ 0x2: 'PowerPC', 0xa: 'i386'}.get(sysa,'') + versioninfo=('', '', '') return release,versioninfo,machine def _mac_ver_xml(): @@ -988,11 +1003,12 @@ """ Interface to the system's uname command. """ - if sys.platform in ('dos','win32','win16','os2'): + if sys.platform in ('dos','win32','win16','os2') or \ + (sys.platform.startswith('java') and os._name == 'nt'): # XXX Others too ? return default try: - f = os.popen('uname %s 2> /dev/null' % option) + f = os.popen('uname %s 2> %s' % (option, DEV_NULL)) except (AttributeError,os.error): return default output = string.strip(f.read()) @@ -1012,16 +1028,38 @@ case the command should fail. """ + + # We do the import here to avoid a bootstrap issue. + # See c73b90b6dadd changeset. + # + # [..] + # ranlib libpython2.7.a + # gcc -o python \ + # Modules/python.o \ + # libpython2.7.a -lsocket -lnsl -ldl -lm + # Traceback (most recent call last): + # File "./setup.py", line 8, in + # from platform import machine as platform_machine + # File "[..]/build/Lib/platform.py", line 116, in + # import sys,string,os,re,subprocess + # File "[..]/build/Lib/subprocess.py", line 429, in + # import select + # ImportError: No module named select + + import subprocess + if sys.platform in ('dos','win32','win16','os2'): # XXX Others too ? return default - target = _follow_symlinks(target).replace('"', '\\"') + target = _follow_symlinks(target) try: - f = os.popen('file "%s" 2> /dev/null' % target) + proc = subprocess.Popen(['file', target], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except (AttributeError,os.error): return default - output = string.strip(f.read()) - rc = f.close() + output = proc.communicate()[0] + rc = proc.wait() if not output or rc: return default else: @@ -1164,7 +1202,7 @@ node = _node() machine = '' - use_syscmd_ver = 01 + use_syscmd_ver = 1 # Try win32_ver() on win32 platforms if system == 'win32': @@ -1176,7 +1214,11 @@ # http://support.microsoft.com/kb/888731 and # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM if not machine: - machine = os.environ.get('PROCESSOR_ARCHITECTURE', '') + # WOW64 processes mask the native architecture + if "PROCESSOR_ARCHITEW6432" in os.environ: + machine = os.environ.get("PROCESSOR_ARCHITEW6432", '') + else: + machine = os.environ.get('PROCESSOR_ARCHITECTURE', '') if not processor: processor = os.environ.get('PROCESSOR_IDENTIFIER', machine) @@ -1216,10 +1258,6 @@ if not version: version = vendor - elif os.name == 'mac': - release,(version,stage,nonrel),machine = mac_ver() - system = 'MacOS' - # System specific extensions if system == 'OpenVMS': # OpenVMS seems to have release and version mixed up @@ -1339,6 +1377,11 @@ '(?: \(([\d\.]+)\))?' ' on (.NET [\d\.]+)') +_pypy_sys_version_parser = re.compile( + r'([\w.+]+)\s*' + '\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*' + '\[PyPy [^\]]+\]?') + _sys_version_cache = {} def _sys_version(sys_version=None): @@ -1400,6 +1443,16 @@ buildno = '' builddate = '' + elif "PyPy" in sys_version: + # PyPy + name = "PyPy" + match = _pypy_sys_version_parser.match(sys_version) + if match is None: + raise ValueError("failed to parse PyPy sys.version: %s" % + repr(sys_version)) + version, buildno, builddate, buildtime = match.groups() + compiler = "" + else: # CPython match = _sys_version_parser.match(sys_version) @@ -1409,15 +1462,16 @@ repr(sys_version)) version, buildno, builddate, buildtime, compiler = \ match.groups() - if hasattr(sys, 'subversion'): - # sys.subversion was added in Python 2.5 - name, branch, revision = sys.subversion - else: - name = 'CPython' - branch = '' - revision = '' + name = 'CPython' builddate = builddate + ' ' + buildtime + if hasattr(sys, 'subversion'): + # sys.subversion was added in Python 2.5 + _, branch, revision = sys.subversion + else: + branch = '' + revision = '' + # Add the patchlevel version if missing l = string.split(version, '.') if len(l) == 2: @@ -1429,29 +1483,15 @@ _sys_version_cache[sys_version] = result return result -def _test_sys_version(): - - _sys_version_cache.clear() - for input, output in ( - ('2.4.3 (#1, Jun 21 2006, 13:54:21) \n[GCC 3.3.4 (pre 3.3.5 20040809)]', - ('CPython', '2.4.3', '', '', '1', 'Jun 21 2006 13:54:21', 'GCC 3.3.4 (pre 3.3.5 20040809)')), - ('IronPython 1.0.60816 on .NET 2.0.50727.42', - ('IronPython', '1.0.60816', '', '', '', '', '.NET 2.0.50727.42')), - ('IronPython 1.0 (1.0.61005.1977) on .NET 2.0.50727.42', - ('IronPython', '1.0.0', '', '', '', '', '.NET 2.0.50727.42')), - ): - parsed = _sys_version(input) - if parsed != output: - print (input, parsed) - def python_implementation(): """ Returns a string identifying the Python implementation. Currently, the following implementations are identified: - 'CPython' (C implementation of Python), - 'IronPython' (.NET implementation of Python), - 'Jython' (Java implementation of Python). + 'CPython' (C implementation of Python), + 'IronPython' (.NET implementation of Python), + 'Jython' (Java implementation of Python), + 'PyPy' (Python implementation of Python). """ return _sys_version()[0] diff --git a/Lib/socket.py b/Lib/socket.py --- a/Lib/socket.py +++ b/Lib/socket.py @@ -162,26 +162,24 @@ from functools import wraps # Used to map java exceptions to the equivalent python exception +# And to set the _last_error attribute on socket objects, to support SO_ERROR def raises_java_exception(method_or_function): @wraps(method_or_function) - def map_exception(*args, **kwargs): + def handle_exception(*args, **kwargs): + is_socket = (len(args) > 0 and isinstance(args[0], _nonblocking_api_mixin)) try: - return method_or_function(*args, **kwargs) - except java.lang.Exception, jlx: - raise _map_exception(jlx) - return map_exception - -# Used for SO_ERROR support. -def raises_error(method): - @wraps(method) - def set_last_error(obj, *args, **kwargs): - try: - setattr(obj, '_last_error', 0) - return method(obj, *args, **kwargs) + try: + return method_or_function(*args, **kwargs) + except java.lang.Exception, jlx: + raise _map_exception(jlx) except error, e: - setattr(obj, '_last_error', e[0]) + if is_socket: + setattr(args[0], '_last_error', e[0]) raise - return set_last_error + else: + if is_socket: + setattr(args[0], '_last_error', 0) + return handle_exception _feature_support_map = { 'ipv6': True, @@ -337,7 +335,7 @@ 'htonl', 'ntohs', 'ntohl', 'inet_pton', 'inet_ntop', 'inet_aton', 'inet_ntoa', 'create_connection', 'socket', 'ssl', # exceptions - 'error', 'herror', 'gaierror', 'timeout', 'sslerror, + 'error', 'herror', 'gaierror', 'timeout', 'sslerror', # classes 'SocketType', # Misc flags @@ -695,6 +693,7 @@ addrs.append(asPyString(addr.getHostAddress())) return (names, addrs) + at raises_java_exception def getfqdn(name=None): """ Return a fully qualified domain name for name. If name is omitted or empty @@ -1145,7 +1144,6 @@ def getblocking(self): return self.mode == MODE_BLOCKING - @raises_error @raises_java_exception def setsockopt(self, level, optname, value): if self.sock_impl: @@ -1168,7 +1166,6 @@ else: return self.pending_options.get( (level, optname), None) - @raises_error @raises_java_exception def shutdown(self, how): assert how in (SHUT_RD, SHUT_WR, SHUT_RDWR) @@ -1176,13 +1173,11 @@ raise error(errno.ENOTCONN, "Transport endpoint is not connected") self.sock_impl.shutdown(how) - @raises_error @raises_java_exception def close(self): if self.sock_impl: self.sock_impl.close() - @raises_error @raises_java_exception def getsockname(self): if self.sock_impl is None: @@ -1194,7 +1189,6 @@ raise error(errno.EINVAL, "Invalid argument") return self.sock_impl.getsockname() - @raises_error @raises_java_exception def getpeername(self): if self.sock_impl is None: @@ -1239,7 +1233,6 @@ return self.server return _nonblocking_api_mixin.getsockopt(self, level, optname) - @raises_error @raises_java_exception def bind(self, addr): assert not self.sock_impl @@ -1248,7 +1241,6 @@ _get_jsockaddr(addr, self.family, self.type, self.proto, AI_PASSIVE) self.local_addr = addr - @raises_error @raises_java_exception def listen(self, backlog): "This signifies a server socket" @@ -1258,7 +1250,6 @@ backlog, self.pending_options[ (SOL_SOCKET, SO_REUSEADDR) ]) self._config() - @raises_error @raises_java_exception def accept(self): "This signifies a server socket" @@ -1283,14 +1274,12 @@ self._config() # Configure timeouts, etc, now that the socket exists self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) - @raises_error @raises_java_exception def connect(self, addr): "This signifies a client socket" self._do_connect(addr) self._setup() - @raises_error @raises_java_exception def connect_ex(self, addr): "This signifies a client socket" @@ -1308,7 +1297,6 @@ self.istream = self.sock_impl.jsocket.getInputStream() self.ostream = self.sock_impl.jsocket.getOutputStream() - @raises_error @raises_java_exception def recv(self, n): if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') @@ -1326,12 +1314,10 @@ data = data[:m] return data.tostring() - @raises_error @raises_java_exception def recvfrom(self, n): - return self.recv(n), None + return self.recv(n), self.getpeername() - @raises_error @raises_java_exception def send(self, s): if not self.sock_impl: raise error(errno.ENOTCONN, 'Socket is not connected') @@ -1344,7 +1330,6 @@ sendall = send - @raises_error @raises_java_exception def close(self): if self.istream: @@ -1365,7 +1350,6 @@ def __init__(self): _nonblocking_api_mixin.__init__(self) - @raises_error @raises_java_exception def bind(self, addr): assert not self.sock_impl @@ -1385,19 +1369,16 @@ self.sock_impl.connect(_get_jsockaddr(addr, self.family, self.type, self.proto, 0)) self.connected = True - @raises_error @raises_java_exception def connect(self, addr): self._do_connect(addr) - @raises_error @raises_java_exception def connect_ex(self, addr): if not self.sock_impl: self._do_connect(addr) return 0 - @raises_error @raises_java_exception def sendto(self, data, p1, p2=None): if not p2: @@ -1417,7 +1398,6 @@ byte_array = java.lang.String(data).getBytes('iso-8859-1') return self.sock_impl.send(byte_array, flags) - @raises_error @raises_java_exception def recvfrom(self, num_bytes, flags=None): """ @@ -1437,7 +1417,6 @@ self._config() return self.sock_impl.recvfrom(num_bytes, flags) - @raises_error @raises_java_exception def recv(self, num_bytes, flags=None): if not self.sock_impl: @@ -1857,11 +1836,6 @@ java_ssl_socket.startHandshake() return java_ssl_socket - def __getattr__(self, attr_name): - if hasattr(self.jython_socket_wrapper, attr_name): - return getattr(self.jython_socket_wrapper, attr_name) - raise AttributeError(attr_name) - @raises_java_exception def read(self, n=4096): data = jarray.zeros(n, 'b') @@ -1872,24 +1846,35 @@ data = data[:m] return data.tostring() + recv = read + @raises_java_exception def write(self, s): self._out_buf.write(s) self._out_buf.flush() return len(s) - @raises_java_exception + send = sendall = write + + def makefile(self, mode='r', bufsize=-1): + return _fileobject(self, mode, bufsize) + def _get_server_cert(self): return self.java_ssl_socket.getSession().getPeerCertificates()[0] + @raises_java_exception def server(self): cert = self._get_server_cert() return cert.getSubjectDN().toString() + @raises_java_exception def issuer(self): cert = self._get_server_cert() return cert.getIssuerDN().toString() + def close(self): + self.jython_socket_wrapper.close() + def test(): s = socket(AF_INET, SOCK_STREAM) s.connect(("", 80)) diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py --- a/Lib/test/string_tests.py +++ b/Lib/test/string_tests.py @@ -1274,11 +1274,9 @@ self.checkequal('Abc', 'abc', 'translate', table) self.checkequal('xyz', 'xyz', 'translate', table) self.checkequal('yz', 'xyz', 'translate', table, 'x') - #FIXME: - if not test_support.is_jython: - self.checkequal('yx', 'zyzzx', 'translate', None, 'z') - self.checkequal('zyzzx', 'zyzzx', 'translate', None, '') - self.checkequal('zyzzx', 'zyzzx', 'translate', None) + self.checkequal('yx', 'zyzzx', 'translate', None, 'z') + self.checkequal('zyzzx', 'zyzzx', 'translate', None, '') + self.checkequal('zyzzx', 'zyzzx', 'translate', None) self.checkraises(ValueError, 'xyz', 'translate', 'too short', 'strip') self.checkraises(ValueError, 'xyz', 'translate', 'too short') diff --git a/Lib/test/test_charmapcodec.py b/Lib/test/test_charmapcodec.py new file mode 100644 --- /dev/null +++ b/Lib/test/test_charmapcodec.py @@ -0,0 +1,61 @@ +""" Python character mapping codec test + +This uses the test codec in testcodec.py and thus also tests the +encodings package lookup scheme. + +Written by Marc-Andre Lemburg (mal at lemburg.com). + +(c) Copyright 2000 Guido van Rossum. + +"""#" + +import test.test_support, unittest + +import codecs + +# Register a search function which knows about our codec +def codec_search_function(encoding): + if encoding == 'testcodec': + from test import testcodec + return tuple(testcodec.getregentry()) + return None + +codecs.register(codec_search_function) + +# test codec's name (see test/testcodec.py) +codecname = 'testcodec' + +class CharmapCodecTest(unittest.TestCase): + def test_constructorx(self): + self.assertEqual(unicode('abc', codecname), u'abc') + self.assertEqual(unicode('xdef', codecname), u'abcdef') + self.assertEqual(unicode('defx', codecname), u'defabc') + self.assertEqual(unicode('dxf', codecname), u'dabcf') + self.assertEqual(unicode('dxfx', codecname), u'dabcfabc') + + def test_encodex(self): + self.assertEqual(u'abc'.encode(codecname), 'abc') + self.assertEqual(u'xdef'.encode(codecname), 'abcdef') + self.assertEqual(u'defx'.encode(codecname), 'defabc') + self.assertEqual(u'dxf'.encode(codecname), 'dabcf') + self.assertEqual(u'dxfx'.encode(codecname), 'dabcfabc') + + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(test.test_support.is_jython, + "FIXME: Currently not working on jython") + def test_constructory(self): + self.assertEqual(unicode('ydef', codecname), u'def') + self.assertEqual(unicode('defy', codecname), u'def') + self.assertEqual(unicode('dyf', codecname), u'df') + self.assertEqual(unicode('dyfy', codecname), u'df') + + def test_maptoundefined(self): + self.assertRaises(UnicodeError, unicode, 'abc\001', codecname) + +def test_main(): + test.test_support.run_unittest(CharmapCodecTest) + +if __name__ == "__main__": + test_main() diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -1084,7 +1084,7 @@ except Exception,e: raise test_support.TestFailed("Test 3.%d: %s" % (pos+1, str(e))) - at unittest.skipIf(test_support.is_jython, "FIXME: Jython issue 2000 missing support for IDNA") + at unittest.skipIf(test_support.is_jython, "FIXME: Jython issue 1153 missing support for IDNA") class IDNACodecTest(unittest.TestCase): def test_builtin_decode(self): self.assertEqual(unicode("python.org", "idna"), u"python.org") diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -518,9 +518,7 @@ s &= WithSet('cdef') # This used to fail self.assertEqual(set(s), set('cd')) - @unittest.skipIf(test_support.is_jython, "FIXME: doesn't work in Jython") def test_issue_4920(self): - # MutableSet.pop() method did not work class MySet(collections.MutableSet): __slots__=['__s'] def __init__(self,items=None): @@ -543,8 +541,9 @@ return result def __repr__(self): return "MySet(%s)" % repr(list(self)) - s = MySet([5,43,2,1]) - self.assertEqual(s.pop(), 1) + values = [5,43,2,1] + s = MySet(values) + self.assertIn(s.pop(), values) def test_issue8750(self): empty = WithSet() diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py deleted file mode 100644 --- a/Lib/test/test_csv.py +++ /dev/null @@ -1,1072 +0,0 @@ -# -*- coding: iso-8859-1 -*- -# Copyright (C) 2001,2002 Python Software Foundation -# csv package unit tests - -import sys -import os -import unittest -from StringIO import StringIO -import tempfile -import csv -import gc -import io -from test import test_support - -class Test_Csv(unittest.TestCase): - """ - Test the underlying C csv parser in ways that are not appropriate - from the high level interface. Further tests of this nature are done - in TestDialectRegistry. - """ - def _test_arg_valid(self, ctor, arg): - self.assertRaises(TypeError, ctor) - self.assertRaises(TypeError, ctor, None) - self.assertRaises(TypeError, ctor, arg, bad_attr = 0) - self.assertRaises(TypeError, ctor, arg, delimiter = 0) - self.assertRaises(TypeError, ctor, arg, delimiter = 'XX') - self.assertRaises(csv.Error, ctor, arg, 'foo') - self.assertRaises(TypeError, ctor, arg, delimiter=None) - self.assertRaises(TypeError, ctor, arg, delimiter=1) - self.assertRaises(TypeError, ctor, arg, quotechar=1) - self.assertRaises(TypeError, ctor, arg, lineterminator=None) - self.assertRaises(TypeError, ctor, arg, lineterminator=1) - self.assertRaises(TypeError, ctor, arg, quoting=None) - self.assertRaises(TypeError, ctor, arg, - quoting=csv.QUOTE_ALL, quotechar='') - self.assertRaises(TypeError, ctor, arg, - quoting=csv.QUOTE_ALL, quotechar=None) - - def test_reader_arg_valid(self): - self._test_arg_valid(csv.reader, []) - - def test_writer_arg_valid(self): - self._test_arg_valid(csv.writer, StringIO()) - - def _test_default_attrs(self, ctor, *args): - obj = ctor(*args) - # Check defaults - self.assertEqual(obj.dialect.delimiter, ',') - self.assertEqual(obj.dialect.doublequote, True) - self.assertEqual(obj.dialect.escapechar, None) - self.assertEqual(obj.dialect.lineterminator, "\r\n") - self.assertEqual(obj.dialect.quotechar, '"') - self.assertEqual(obj.dialect.quoting, csv.QUOTE_MINIMAL) - self.assertEqual(obj.dialect.skipinitialspace, False) - self.assertEqual(obj.dialect.strict, False) - # Try deleting or changing attributes (they are read-only) - self.assertRaises(TypeError, delattr, obj.dialect, 'delimiter') - self.assertRaises(TypeError, setattr, obj.dialect, 'delimiter', ':') - self.assertRaises(AttributeError, delattr, obj.dialect, 'quoting') - self.assertRaises(AttributeError, setattr, obj.dialect, - 'quoting', None) - - def test_reader_attrs(self): - self._test_default_attrs(csv.reader, []) - - def test_writer_attrs(self): - self._test_default_attrs(csv.writer, StringIO()) - - def _test_kw_attrs(self, ctor, *args): - # Now try with alternate options - kwargs = dict(delimiter=':', doublequote=False, escapechar='\\', - lineterminator='\r', quotechar='*', - quoting=csv.QUOTE_NONE, skipinitialspace=True, - strict=True) - obj = ctor(*args, **kwargs) - self.assertEqual(obj.dialect.delimiter, ':') - self.assertEqual(obj.dialect.doublequote, False) - self.assertEqual(obj.dialect.escapechar, '\\') - self.assertEqual(obj.dialect.lineterminator, "\r") - self.assertEqual(obj.dialect.quotechar, '*') - self.assertEqual(obj.dialect.quoting, csv.QUOTE_NONE) - self.assertEqual(obj.dialect.skipinitialspace, True) - self.assertEqual(obj.dialect.strict, True) - - def test_reader_kw_attrs(self): - self._test_kw_attrs(csv.reader, []) - - def test_writer_kw_attrs(self): - self._test_kw_attrs(csv.writer, StringIO()) - - def _test_dialect_attrs(self, ctor, *args): - # Now try with dialect-derived options - class dialect: - delimiter='-' - doublequote=False - escapechar='^' - lineterminator='$' - quotechar='#' - quoting=csv.QUOTE_ALL - skipinitialspace=True - strict=False - args = args + (dialect,) - obj = ctor(*args) - self.assertEqual(obj.dialect.delimiter, '-') - self.assertEqual(obj.dialect.doublequote, False) - self.assertEqual(obj.dialect.escapechar, '^') - self.assertEqual(obj.dialect.lineterminator, "$") - self.assertEqual(obj.dialect.quotechar, '#') - self.assertEqual(obj.dialect.quoting, csv.QUOTE_ALL) - self.assertEqual(obj.dialect.skipinitialspace, True) - self.assertEqual(obj.dialect.strict, False) - - def test_reader_dialect_attrs(self): - self._test_dialect_attrs(csv.reader, []) - - def test_writer_dialect_attrs(self): - self._test_dialect_attrs(csv.writer, StringIO()) - - - def _write_test(self, fields, expect, **kwargs): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, **kwargs) - writer.writerow(fields) - fileobj.seek(0) - self.assertEqual(fileobj.read(), - expect + writer.dialect.lineterminator) - finally: - fileobj.close() - os.unlink(name) - - def test_write_arg_valid(self): - self.assertRaises(csv.Error, self._write_test, None, '') - self._write_test((), '') - self._write_test([None], '""') - self.assertRaises(csv.Error, self._write_test, - [None], None, quoting = csv.QUOTE_NONE) - # Check that exceptions are passed up the chain - class BadList: - def __len__(self): - return 10; - def __getitem__(self, i): - if i > 2: - raise IOError - self.assertRaises(IOError, self._write_test, BadList(), '') - class BadItem: - def __str__(self): - raise IOError - self.assertRaises(IOError, self._write_test, [BadItem()], '') - - def test_write_bigfield(self): - # This exercises the buffer realloc functionality - bigstring = 'X' * 50000 - self._write_test([bigstring,bigstring], '%s,%s' % \ - (bigstring, bigstring)) - - def test_write_quoting(self): - self._write_test(['a',1,'p,q'], 'a,1,"p,q"') - self.assertRaises(csv.Error, - self._write_test, - ['a',1,'p,q'], 'a,1,p,q', - quoting = csv.QUOTE_NONE) - self._write_test(['a',1,'p,q'], 'a,1,"p,q"', - quoting = csv.QUOTE_MINIMAL) - self._write_test(['a',1,'p,q'], '"a",1,"p,q"', - quoting = csv.QUOTE_NONNUMERIC) - self._write_test(['a',1,'p,q'], '"a","1","p,q"', - quoting = csv.QUOTE_ALL) - self._write_test(['a\nb',1], '"a\nb","1"', - quoting = csv.QUOTE_ALL) - - def test_write_escape(self): - self._write_test(['a',1,'p,q'], 'a,1,"p,q"', - escapechar='\\') - self.assertRaises(csv.Error, - self._write_test, - ['a',1,'p,"q"'], 'a,1,"p,\\"q\\""', - escapechar=None, doublequote=False) - self._write_test(['a',1,'p,"q"'], 'a,1,"p,\\"q\\""', - escapechar='\\', doublequote = False) - self._write_test(['"'], '""""', - escapechar='\\', quoting = csv.QUOTE_MINIMAL) - self._write_test(['"'], '\\"', - escapechar='\\', quoting = csv.QUOTE_MINIMAL, - doublequote = False) - self._write_test(['"'], '\\"', - escapechar='\\', quoting = csv.QUOTE_NONE) - self._write_test(['a',1,'p,q'], 'a,1,p\\,q', - escapechar='\\', quoting = csv.QUOTE_NONE) - - def test_writerows(self): - class BrokenFile: - def write(self, buf): - raise IOError - writer = csv.writer(BrokenFile()) - self.assertRaises(IOError, writer.writerows, [['a']]) - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj) - self.assertRaises(TypeError, writer.writerows, None) - writer.writerows([['a','b'],['c','d']]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "a,b\r\nc,d\r\n") - finally: - fileobj.close() - os.unlink(name) - - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") - def test_write_float(self): - # Issue 13573: loss of precision because csv.writer - # uses str() for floats instead of repr() - orig_row = [1.234567890123, 1.0/7.0, 'abc'] - f = StringIO() - c = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC) - c.writerow(orig_row) - f.seek(0) - c = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC) - new_row = next(c) - self.assertEqual(orig_row, new_row) - - def _read_test(self, input, expect, **kwargs): - reader = csv.reader(input, **kwargs) - result = list(reader) - self.assertEqual(result, expect) - - def test_read_oddinputs(self): - self._read_test([], []) - self._read_test([''], [[]]) - self.assertRaises(csv.Error, self._read_test, - ['"ab"c'], None, strict = 1) - # cannot handle null bytes for the moment - self.assertRaises(csv.Error, self._read_test, - ['ab\0c'], None, strict = 1) - self._read_test(['"ab"c'], [['abc']], doublequote = 0) - - def test_read_eol(self): - self._read_test(['a,b'], [['a','b']]) - self._read_test(['a,b\n'], [['a','b']]) - self._read_test(['a,b\r\n'], [['a','b']]) - self._read_test(['a,b\r'], [['a','b']]) - self.assertRaises(csv.Error, self._read_test, ['a,b\rc,d'], []) - self.assertRaises(csv.Error, self._read_test, ['a,b\nc,d'], []) - self.assertRaises(csv.Error, self._read_test, ['a,b\r\nc,d'], []) - - def test_read_escape(self): - self._read_test(['a,\\b,c'], [['a', 'b', 'c']], escapechar='\\') - self._read_test(['a,b\\,c'], [['a', 'b,c']], escapechar='\\') - self._read_test(['a,"b\\,c"'], [['a', 'b,c']], escapechar='\\') - self._read_test(['a,"b,\\c"'], [['a', 'b,c']], escapechar='\\') - self._read_test(['a,"b,c\\""'], [['a', 'b,c"']], escapechar='\\') - self._read_test(['a,"b,c"\\'], [['a', 'b,c\\']], escapechar='\\') - - def test_read_quoting(self): - self._read_test(['1,",3,",5'], [['1', ',3,', '5']]) - self._read_test(['1,",3,",5'], [['1', '"', '3', '"', '5']], - quotechar=None, escapechar='\\') - self._read_test(['1,",3,",5'], [['1', '"', '3', '"', '5']], - quoting=csv.QUOTE_NONE, escapechar='\\') - # will this fail where locale uses comma for decimals? - self._read_test([',3,"5",7.3, 9'], [['', 3, '5', 7.3, 9]], - quoting=csv.QUOTE_NONNUMERIC) - self._read_test(['"a\nb", 7'], [['a\nb', ' 7']]) - self.assertRaises(ValueError, self._read_test, - ['abc,3'], [[]], - quoting=csv.QUOTE_NONNUMERIC) - - def test_read_bigfield(self): - # This exercises the buffer realloc functionality and field size - # limits. - limit = csv.field_size_limit() - try: - size = 50000 - bigstring = 'X' * size - bigline = '%s,%s' % (bigstring, bigstring) - self._read_test([bigline], [[bigstring, bigstring]]) - csv.field_size_limit(size) - self._read_test([bigline], [[bigstring, bigstring]]) - self.assertEqual(csv.field_size_limit(), size) - csv.field_size_limit(size-1) - self.assertRaises(csv.Error, self._read_test, [bigline], []) - self.assertRaises(TypeError, csv.field_size_limit, None) - self.assertRaises(TypeError, csv.field_size_limit, 1, None) - finally: - csv.field_size_limit(limit) - - def test_read_linenum(self): - for r in (csv.reader(['line,1', 'line,2', 'line,3']), - csv.DictReader(['line,1', 'line,2', 'line,3'], - fieldnames=['a', 'b', 'c'])): - self.assertEqual(r.line_num, 0) - r.next() - self.assertEqual(r.line_num, 1) - r.next() - self.assertEqual(r.line_num, 2) - r.next() - self.assertEqual(r.line_num, 3) - self.assertRaises(StopIteration, r.next) - self.assertEqual(r.line_num, 3) - - def test_roundtrip_quoteed_newlines(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj) - self.assertRaises(TypeError, writer.writerows, None) - rows = [['a\nb','b'],['c','x\r\nd']] - writer.writerows(rows) - fileobj.seek(0) - for i, row in enumerate(csv.reader(fileobj)): - self.assertEqual(row, rows[i]) - finally: - fileobj.close() - os.unlink(name) - -class TestDialectRegistry(unittest.TestCase): - def test_registry_badargs(self): - self.assertRaises(TypeError, csv.list_dialects, None) - self.assertRaises(TypeError, csv.get_dialect) - self.assertRaises(csv.Error, csv.get_dialect, None) - self.assertRaises(csv.Error, csv.get_dialect, "nonesuch") - self.assertRaises(TypeError, csv.unregister_dialect) - self.assertRaises(csv.Error, csv.unregister_dialect, None) - self.assertRaises(csv.Error, csv.unregister_dialect, "nonesuch") - self.assertRaises(TypeError, csv.register_dialect, None) - self.assertRaises(TypeError, csv.register_dialect, None, None) - self.assertRaises(TypeError, csv.register_dialect, "nonesuch", 0, 0) - self.assertRaises(TypeError, csv.register_dialect, "nonesuch", - badargument=None) - self.assertRaises(TypeError, csv.register_dialect, "nonesuch", - quoting=None) - self.assertRaises(TypeError, csv.register_dialect, []) - - def test_registry(self): - class myexceltsv(csv.excel): - delimiter = "\t" - name = "myexceltsv" - expected_dialects = csv.list_dialects() + [name] - expected_dialects.sort() - csv.register_dialect(name, myexceltsv) - self.addCleanup(csv.unregister_dialect, name) - self.assertEqual(csv.get_dialect(name).delimiter, '\t') - got_dialects = sorted(csv.list_dialects()) - self.assertEqual(expected_dialects, got_dialects) - - def test_register_kwargs(self): - name = 'fedcba' - csv.register_dialect(name, delimiter=';') - self.addCleanup(csv.unregister_dialect, name) - self.assertEqual(csv.get_dialect(name).delimiter, ';') - self.assertEqual([['X', 'Y', 'Z']], list(csv.reader(['X;Y;Z'], name))) - - def test_incomplete_dialect(self): - class myexceltsv(csv.Dialect): - delimiter = "\t" - self.assertRaises(csv.Error, myexceltsv) - - def test_space_dialect(self): - class space(csv.excel): - delimiter = " " - quoting = csv.QUOTE_NONE - escapechar = "\\" - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("abc def\nc1ccccc1 benzene\n") - fileobj.seek(0) - rdr = csv.reader(fileobj, dialect=space()) - self.assertEqual(rdr.next(), ["abc", "def"]) - self.assertEqual(rdr.next(), ["c1ccccc1", "benzene"]) - finally: - fileobj.close() - os.unlink(name) - - def test_dialect_apply(self): - class testA(csv.excel): - delimiter = "\t" - class testB(csv.excel): - delimiter = ":" - class testC(csv.excel): - delimiter = "|" - - csv.register_dialect('testC', testC) - try: - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj) - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1,2,3\r\n") - finally: - fileobj.close() - os.unlink(name) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, testA) - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1\t2\t3\r\n") - finally: - fileobj.close() - os.unlink(name) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect=testB()) - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1:2:3\r\n") - finally: - fileobj.close() - os.unlink(name) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect='testC') - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1|2|3\r\n") - finally: - fileobj.close() - os.unlink(name) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect=testA, delimiter=';') - writer.writerow([1,2,3]) - fileobj.seek(0) - self.assertEqual(fileobj.read(), "1;2;3\r\n") - finally: - fileobj.close() - os.unlink(name) - - finally: - csv.unregister_dialect('testC') - - def test_bad_dialect(self): - # Unknown parameter - self.assertRaises(TypeError, csv.reader, [], bad_attr = 0) - # Bad values - self.assertRaises(TypeError, csv.reader, [], delimiter = None) - self.assertRaises(TypeError, csv.reader, [], quoting = -1) - self.assertRaises(TypeError, csv.reader, [], quoting = 100) - -class TestCsvBase(unittest.TestCase): - def readerAssertEqual(self, input, expected_result): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write(input) - fileobj.seek(0) - reader = csv.reader(fileobj, dialect = self.dialect) - fields = list(reader) - self.assertEqual(fields, expected_result) - finally: - fileobj.close() - os.unlink(name) - - def writerAssertEqual(self, input, expected_result): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect = self.dialect) - writer.writerows(input) - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected_result) - finally: - fileobj.close() - os.unlink(name) - -class TestDialectExcel(TestCsvBase): - dialect = 'excel' - - def test_single(self): - self.readerAssertEqual('abc', [['abc']]) - - def test_simple(self): - self.readerAssertEqual('1,2,3,4,5', [['1','2','3','4','5']]) - - def test_blankline(self): - self.readerAssertEqual('', []) - - def test_empty_fields(self): - self.readerAssertEqual(',', [['', '']]) - - def test_singlequoted(self): - self.readerAssertEqual('""', [['']]) - - def test_singlequoted_left_empty(self): - self.readerAssertEqual('"",', [['','']]) - - def test_singlequoted_right_empty(self): - self.readerAssertEqual(',""', [['','']]) - - def test_single_quoted_quote(self): - self.readerAssertEqual('""""', [['"']]) - - def test_quoted_quotes(self): - self.readerAssertEqual('""""""', [['""']]) - - def test_inline_quote(self): - self.readerAssertEqual('a""b', [['a""b']]) - - def test_inline_quotes(self): - self.readerAssertEqual('a"b"c', [['a"b"c']]) - - def test_quotes_and_more(self): - # Excel would never write a field containing '"a"b', but when - # reading one, it will return 'ab'. - self.readerAssertEqual('"a"b', [['ab']]) - - def test_lone_quote(self): - self.readerAssertEqual('a"b', [['a"b']]) - - def test_quote_and_quote(self): - # Excel would never write a field containing '"a" "b"', but when - # reading one, it will return 'a "b"'. - self.readerAssertEqual('"a" "b"', [['a "b"']]) - - def test_space_and_quote(self): - self.readerAssertEqual(' "a"', [[' "a"']]) - - def test_quoted(self): - self.readerAssertEqual('1,2,3,"I think, therefore I am",5,6', - [['1', '2', '3', - 'I think, therefore I am', - '5', '6']]) - - def test_quoted_quote(self): - self.readerAssertEqual('1,2,3,"""I see,"" said the blind man","as he picked up his hammer and saw"', - [['1', '2', '3', - '"I see," said the blind man', - 'as he picked up his hammer and saw']]) - - def test_quoted_nl(self): - input = '''\ -1,2,3,"""I see,"" -said the blind man","as he picked up his -hammer and saw" -9,8,7,6''' - self.readerAssertEqual(input, - [['1', '2', '3', - '"I see,"\nsaid the blind man', - 'as he picked up his\nhammer and saw'], - ['9','8','7','6']]) - - def test_dubious_quote(self): - self.readerAssertEqual('12,12,1",', [['12', '12', '1"', '']]) - - def test_null(self): - self.writerAssertEqual([], '') - - def test_single_writer(self): - self.writerAssertEqual([['abc']], 'abc\r\n') - - def test_simple_writer(self): - self.writerAssertEqual([[1, 2, 'abc', 3, 4]], '1,2,abc,3,4\r\n') - - def test_quotes(self): - self.writerAssertEqual([[1, 2, 'a"bc"', 3, 4]], '1,2,"a""bc""",3,4\r\n') - - def test_quote_fieldsep(self): - self.writerAssertEqual([['abc,def']], '"abc,def"\r\n') - - def test_newlines(self): - self.writerAssertEqual([[1, 2, 'a\nbc', 3, 4]], '1,2,"a\nbc",3,4\r\n') - -class EscapedExcel(csv.excel): - quoting = csv.QUOTE_NONE - escapechar = '\\' - -class TestEscapedExcel(TestCsvBase): - dialect = EscapedExcel() - - def test_escape_fieldsep(self): - self.writerAssertEqual([['abc,def']], 'abc\\,def\r\n') - - def test_read_escape_fieldsep(self): - self.readerAssertEqual('abc\\,def\r\n', [['abc,def']]) - -class QuotedEscapedExcel(csv.excel): - quoting = csv.QUOTE_NONNUMERIC - escapechar = '\\' - -class TestQuotedEscapedExcel(TestCsvBase): - dialect = QuotedEscapedExcel() - - def test_write_escape_fieldsep(self): - self.writerAssertEqual([['abc,def']], '"abc,def"\r\n') - - def test_read_escape_fieldsep(self): - self.readerAssertEqual('"abc\\,def"\r\n', [['abc,def']]) - -class TestDictFields(unittest.TestCase): - ### "long" means the row is longer than the number of fieldnames - ### "short" means there are fewer elements in the row than fieldnames - - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") - def test_write_simple_dict(self): - fd, name = tempfile.mkstemp() - fileobj = io.open(fd, 'w+b') - try: - writer = csv.DictWriter(fileobj, fieldnames = ["f1", "f2", "f3"]) - writer.writeheader() - fileobj.seek(0) - self.assertEqual(fileobj.readline(), "f1,f2,f3\r\n") - writer.writerow({"f1": 10, "f3": "abc"}) - fileobj.seek(0) - fileobj.readline() # header - self.assertEqual(fileobj.read(), "10,,abc\r\n") - finally: - fileobj.close() - os.unlink(name) - - def test_write_no_fields(self): - fileobj = StringIO() - self.assertRaises(TypeError, csv.DictWriter, fileobj) - - def test_read_dict_fields(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("1,2,abc\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, - fieldnames=["f1", "f2", "f3"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_dict_no_fieldnames(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("f1,f2,f3\r\n1,2,abc\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj) - self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'}) - finally: - fileobj.close() - os.unlink(name) - - # Two test cases to make sure existing ways of implicitly setting - # fieldnames continue to work. Both arise from discussion in issue3436. - def test_read_dict_fieldnames_from_file(self): - fd, name = tempfile.mkstemp() - f = os.fdopen(fd, "w+b") - try: - f.write("f1,f2,f3\r\n1,2,abc\r\n") - f.seek(0) - reader = csv.DictReader(f, fieldnames=csv.reader(f).next()) - self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', "f3": 'abc'}) - finally: - f.close() - os.unlink(name) - - def test_read_dict_fieldnames_chain(self): - import itertools - fd, name = tempfile.mkstemp() - f = os.fdopen(fd, "w+b") - try: - f.write("f1,f2,f3\r\n1,2,abc\r\n") - f.seek(0) - reader = csv.DictReader(f) - first = next(reader) - for row in itertools.chain([first], reader): - self.assertEqual(reader.fieldnames, ["f1", "f2", "f3"]) - self.assertEqual(row, {"f1": '1', "f2": '2', "f3": 'abc'}) - finally: - f.close() - os.unlink(name) - - def test_read_long(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("1,2,abc,4,5,6\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, - fieldnames=["f1", "f2"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', - None: ["abc", "4", "5", "6"]}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_long_with_rest(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("1,2,abc,4,5,6\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, - fieldnames=["f1", "f2"], restkey="_rest") - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', - "_rest": ["abc", "4", "5", "6"]}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_long_with_rest_no_fieldnames(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("f1,f2\r\n1,2,abc,4,5,6\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, restkey="_rest") - self.assertEqual(reader.fieldnames, ["f1", "f2"]) - self.assertEqual(reader.next(), {"f1": '1', "f2": '2', - "_rest": ["abc", "4", "5", "6"]}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_short(self): - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - fileobj.write("1,2,abc,4,5,6\r\n1,2,abc\r\n") - fileobj.seek(0) - reader = csv.DictReader(fileobj, - fieldnames="1 2 3 4 5 6".split(), - restval="DEFAULT") - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": '4', "5": '5', "6": '6'}) - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": 'DEFAULT', "5": 'DEFAULT', - "6": 'DEFAULT'}) - finally: - fileobj.close() - os.unlink(name) - - def test_read_multi(self): - sample = [ - '2147483648,43.0e12,17,abc,def\r\n', - '147483648,43.0e2,17,abc,def\r\n', - '47483648,43.0,170,abc,def\r\n' - ] - - reader = csv.DictReader(sample, - fieldnames="i1 float i2 s1 s2".split()) - self.assertEqual(reader.next(), {"i1": '2147483648', - "float": '43.0e12', - "i2": '17', - "s1": 'abc', - "s2": 'def'}) - - def test_read_with_blanks(self): - reader = csv.DictReader(["1,2,abc,4,5,6\r\n","\r\n", - "1,2,abc,4,5,6\r\n"], - fieldnames="1 2 3 4 5 6".split()) - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": '4', "5": '5', "6": '6'}) - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": '4', "5": '5', "6": '6'}) - - def test_read_semi_sep(self): - reader = csv.DictReader(["1;2;abc;4;5;6\r\n"], - fieldnames="1 2 3 4 5 6".split(), - delimiter=';') - self.assertEqual(reader.next(), {"1": '1', "2": '2', "3": 'abc', - "4": '4', "5": '5', "6": '6'}) - -class TestArrayWrites(unittest.TestCase): - def test_int_write(self): - import array - contents = [(20-i) for i in range(20)] - a = array.array('i', contents) - - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect="excel") - writer.writerow(a) - expected = ",".join([str(i) for i in a])+"\r\n" - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected) - finally: - fileobj.close() - os.unlink(name) - - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") - def test_double_write(self): - import array - contents = [(20-i)*0.1 for i in range(20)] - a = array.array('d', contents) - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect="excel") - writer.writerow(a) - expected = ",".join([repr(i) for i in a])+"\r\n" - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected) - finally: - fileobj.close() - os.unlink(name) - - @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython") - def test_float_write(self): - import array - contents = [(20-i)*0.1 for i in range(20)] - a = array.array('f', contents) - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect="excel") - writer.writerow(a) - expected = ",".join([repr(i) for i in a])+"\r\n" - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected) - finally: - fileobj.close() - os.unlink(name) - - def test_char_write(self): - import array, string - a = array.array('c', string.letters) - fd, name = tempfile.mkstemp() - fileobj = os.fdopen(fd, "w+b") - try: - writer = csv.writer(fileobj, dialect="excel") - writer.writerow(a) - expected = ",".join(a)+"\r\n" - fileobj.seek(0) - self.assertEqual(fileobj.read(), expected) - finally: - fileobj.close() - os.unlink(name) - -class TestDialectValidity(unittest.TestCase): - def test_quoting(self): - class mydialect(csv.Dialect): - delimiter = ";" - escapechar = '\\' - doublequote = False - skipinitialspace = True - lineterminator = '\r\n' - quoting = csv.QUOTE_NONE - d = mydialect() - - mydialect.quoting = None - self.assertRaises(csv.Error, mydialect) - - mydialect.doublequote = True - mydialect.quoting = csv.QUOTE_ALL - mydialect.quotechar = '"' - d = mydialect() - - mydialect.quotechar = "''" - self.assertRaises(csv.Error, mydialect) - - mydialect.quotechar = 4 - self.assertRaises(csv.Error, mydialect) - - def test_delimiter(self): - class mydialect(csv.Dialect): - delimiter = ";" - escapechar = '\\' - doublequote = False - skipinitialspace = True - lineterminator = '\r\n' - quoting = csv.QUOTE_NONE - d = mydialect() - - mydialect.delimiter = ":::" - self.assertRaises(csv.Error, mydialect) - - mydialect.delimiter = 4 - self.assertRaises(csv.Error, mydialect) - - def test_lineterminator(self): - class mydialect(csv.Dialect): - delimiter = ";" - escapechar = '\\' - doublequote = False - skipinitialspace = True - lineterminator = '\r\n' - quoting = csv.QUOTE_NONE - d = mydialect() - - mydialect.lineterminator = ":::" - d = mydialect() - - mydialect.lineterminator = 4 - self.assertRaises(csv.Error, mydialect) - - -class TestSniffer(unittest.TestCase): - sample1 = """\ -Harry's, Arlington Heights, IL, 2/1/03, Kimi Hayes -Shark City, Glendale Heights, IL, 12/28/02, Prezence -Tommy's Place, Blue Island, IL, 12/28/02, Blue Sunday/White Crow -Stonecutters Seafood and Chop House, Lemont, IL, 12/19/02, Week Back -""" - sample2 = """\ -'Harry''s':'Arlington Heights':'IL':'2/1/03':'Kimi Hayes' -'Shark City':'Glendale Heights':'IL':'12/28/02':'Prezence' -'Tommy''s Place':'Blue Island':'IL':'12/28/02':'Blue Sunday/White Crow' -'Stonecutters ''Seafood'' and Chop House':'Lemont':'IL':'12/19/02':'Week Back' -""" - header = '''\ -"venue","city","state","date","performers" -''' - sample3 = '''\ -05/05/03?05/05/03?05/05/03?05/05/03?05/05/03?05/05/03 -05/05/03?05/05/03?05/05/03?05/05/03?05/05/03?05/05/03 -05/05/03?05/05/03?05/05/03?05/05/03?05/05/03?05/05/03 -''' - - sample4 = '''\ -2147483648;43.0e12;17;abc;def -147483648;43.0e2;17;abc;def -47483648;43.0;170;abc;def -''' - - sample5 = "aaa\tbbb\r\nAAA\t\r\nBBB\t\r\n" - sample6 = "a|b|c\r\nd|e|f\r\n" - sample7 = "'a'|'b'|'c'\r\n'd'|e|f\r\n" - - def test_has_header(self): - sniffer = csv.Sniffer() - self.assertEqual(sniffer.has_header(self.sample1), False) - self.assertEqual(sniffer.has_header(self.header+self.sample1), True) - - def test_sniff(self): - sniffer = csv.Sniffer() - dialect = sniffer.sniff(self.sample1) - self.assertEqual(dialect.delimiter, ",") - self.assertEqual(dialect.quotechar, '"') - self.assertEqual(dialect.skipinitialspace, True) - - dialect = sniffer.sniff(self.sample2) - self.assertEqual(dialect.delimiter, ":") - self.assertEqual(dialect.quotechar, "'") - self.assertEqual(dialect.skipinitialspace, False) - - def test_delimiters(self): - sniffer = csv.Sniffer() - dialect = sniffer.sniff(self.sample3) - # given that all three lines in sample3 are equal, - # I think that any character could have been 'guessed' as the - # delimiter, depending on dictionary order - self.assertIn(dialect.delimiter, self.sample3) - dialect = sniffer.sniff(self.sample3, delimiters="?,") - self.assertEqual(dialect.delimiter, "?") - dialect = sniffer.sniff(self.sample3, delimiters="/,") - self.assertEqual(dialect.delimiter, "/") - dialect = sniffer.sniff(self.sample4) - self.assertEqual(dialect.delimiter, ";") - dialect = sniffer.sniff(self.sample5) - self.assertEqual(dialect.delimiter, "\t") - dialect = sniffer.sniff(self.sample6) - self.assertEqual(dialect.delimiter, "|") - dialect = sniffer.sniff(self.sample7) - self.assertEqual(dialect.delimiter, "|") - self.assertEqual(dialect.quotechar, "'") - - def test_doublequote(self): - sniffer = csv.Sniffer() - dialect = sniffer.sniff(self.header) - self.assertFalse(dialect.doublequote) - dialect = sniffer.sniff(self.sample2) - self.assertTrue(dialect.doublequote) - -if not hasattr(sys, "gettotalrefcount"): - if test_support.verbose: print "*** skipping leakage tests ***" -else: - class NUL: - def write(s, *args): - pass - writelines = write - - class TestLeaks(unittest.TestCase): - def test_create_read(self): - delta = 0 - lastrc = sys.gettotalrefcount() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - csv.reader(["a,b,c\r\n"]) - delta = rc-lastrc - lastrc = rc - # if csv.reader() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) - - def test_create_write(self): - delta = 0 - lastrc = sys.gettotalrefcount() - s = NUL() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - csv.writer(s) - csv.writer(s) - csv.writer(s) - delta = rc-lastrc - lastrc = rc - # if csv.writer() leaks, last delta should be 3 or more - self.assertEqual(delta < 3, True) - - def test_read(self): - delta = 0 - rows = ["a,b,c\r\n"]*5 - lastrc = sys.gettotalrefcount() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - rdr = csv.reader(rows) - for row in rdr: - pass - delta = rc-lastrc - lastrc = rc - # if reader leaks during read, delta should be 5 or more - self.assertEqual(delta < 5, True) - - def test_write(self): - delta = 0 - rows = [[1,2,3]]*5 - s = NUL() - lastrc = sys.gettotalrefcount() - for i in xrange(20): - gc.collect() - self.assertEqual(gc.garbage, []) - rc = sys.gettotalrefcount() - writer = csv.writer(s) - for row in rows: - writer.writerow(row) - delta = rc-lastrc - lastrc = rc - # if writer leaks during write, last delta should be 5 or more - self.assertEqual(delta < 5, True) - -# commented out for now - csv module doesn't yet support Unicode -## class TestUnicode(unittest.TestCase): -## def test_unicode_read(self): -## import codecs -## f = codecs.EncodedFile(StringIO("Martin von L?wis," -## "Marc Andr? Lemburg," -## "Guido van Rossum," -## "Fran?ois Pinard\r\n"), -## data_encoding='iso-8859-1') -## reader = csv.reader(f) -## self.assertEqual(list(reader), [[u"Martin von L?wis", -## u"Marc Andr? Lemburg", -## u"Guido van Rossum", -## u"Fran?ois Pinardn"]]) - -def test_main(): - mod = sys.modules[__name__] - test_support.run_unittest( - *[getattr(mod, name) for name in dir(mod) if name.startswith('Test')] - ) - -if __name__ == '__main__': - test_main() diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -1449,6 +1449,18 @@ self.assertEqual(float(d1), 66) self.assertEqual(float(d2), 15.32) + def test_nan_to_float(self): + # Test conversions of decimal NANs to float. + # See http://bugs.python.org/issue15544 + for s in ('nan', 'nan1234', '-nan', '-nan2468'): + f = float(Decimal(s)) + self.assertTrue(math.isnan(f)) + + def test_snan_to_float(self): + for s in ('snan', '-snan', 'snan1357', '-snan1234'): + d = Decimal(s) + self.assertRaises(ValueError, float, d) + def test_eval_round_trip(self): #with zero diff --git a/Lib/test/test_dict_jy.py b/Lib/test/test_dict_jy.py --- a/Lib/test/test_dict_jy.py +++ b/Lib/test/test_dict_jy.py @@ -1,6 +1,7 @@ from test import test_support import java import unittest +from collections import defaultdict class DictInitTest(unittest.TestCase): def testInternalSetitemInInit(self): @@ -93,6 +94,12 @@ def __getitem__(self, key): raise CustomKeyError("custom message") self.assertRaises(CustomKeyError, lambda: DerivedDict()['foo']) + + def test_issue1676(self): + #See http://bugs.jython.org/issue1676 + x=defaultdict() + #This formerly caused an NPE. + self.assertEqual(None, x.pop(None,None)) class JavaIntegrationTest(unittest.TestCase): "Tests for instantiating dicts from Java maps and hashtables" diff --git a/Lib/test/test_httpservers.py b/Lib/test/test_httpservers.py --- a/Lib/test/test_httpservers.py +++ b/Lib/test/test_httpservers.py @@ -4,11 +4,6 @@ Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest. """ -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -from SimpleHTTPServer import SimpleHTTPRequestHandler -from CGIHTTPServer import CGIHTTPRequestHandler -import CGIHTTPServer - import os import sys import re @@ -17,12 +12,17 @@ import urllib import httplib import tempfile +import unittest +import CGIHTTPServer -import unittest +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from SimpleHTTPServer import SimpleHTTPRequestHandler +from CGIHTTPServer import CGIHTTPRequestHandler from StringIO import StringIO +from test import test_support -from test import test_support + threading = test_support.import_module('threading') @@ -43,7 +43,7 @@ self.end_headers() self.wfile.write(b'Data\r\n') - def log_message(self, format, *args): + def log_message(self, fmt, *args): pass @@ -97,9 +97,9 @@ self.handler = SocketlessRequestHandler() def send_typical_request(self, message): - input = StringIO(message) + input_msg = StringIO(message) output = StringIO() - self.handler.rfile = input + self.handler.rfile = input_msg self.handler.wfile = output self.handler.handle_one_request() output.seek(0) @@ -296,7 +296,7 @@ os.chdir(self.cwd) try: shutil.rmtree(self.tempdir) - except: + except OSError: pass finally: BaseTestCase.tearDown(self) @@ -418,42 +418,44 @@ finally: BaseTestCase.tearDown(self) - def test_url_collapse_path_split(self): + def test_url_collapse_path(self): + # verify tail is the last portion and head is the rest on proper urls test_vectors = { - '': ('/', ''), + '': '//', '..': IndexError, '/.//..': IndexError, - '/': ('/', ''), - '//': ('/', ''), - '/\\': ('/', '\\'), - '/.//': ('/', ''), - 'cgi-bin/file1.py': ('/cgi-bin', 'file1.py'), - '/cgi-bin/file1.py': ('/cgi-bin', 'file1.py'), - '/cgi-bin/file1.py/PATH-INFO': ('/cgi-bin', 'file1.py/PATH-INFO'), - 'a': ('/', 'a'), - '/a': ('/', 'a'), - '//a': ('/', 'a'), - './a': ('/', 'a'), - './C:/': ('/C:', ''), - '/a/b': ('/a', 'b'), - '/a/b/': ('/a/b', ''), - '/a/b/c/..': ('/a/b', ''), - '/a/b/c/../d': ('/a/b', 'd'), - '/a/b/c/../d/e/../f': ('/a/b/d', 'f'), - '/a/b/c/../d/e/../../f': ('/a/b', 'f'), - '/a/b/c/../d/e/.././././..//f': ('/a/b', 'f'), + '/': '//', + '//': '//', + '/\\': '//\\', + '/.//': '//', + 'cgi-bin/file1.py': '/cgi-bin/file1.py', + '/cgi-bin/file1.py': '/cgi-bin/file1.py', + 'a': '//a', + '/a': '//a', + '//a': '//a', + './a': '//a', + './C:/': '/C:/', + '/a/b': '/a/b', + '/a/b/': '/a/b/', + '/a/b/.': '/a/b/', + '/a/b/c/..': '/a/b/', + '/a/b/c/../d': '/a/b/d', + '/a/b/c/../d/e/../f': '/a/b/d/f', + '/a/b/c/../d/e/../../f': '/a/b/f', + '/a/b/c/../d/e/.././././..//f': '/a/b/f', '../a/b/c/../d/e/.././././..//f': IndexError, - '/a/b/c/../d/e/../../../f': ('/a', 'f'), - '/a/b/c/../d/e/../../../../f': ('/', 'f'), + '/a/b/c/../d/e/../../../f': '/a/f', + '/a/b/c/../d/e/../../../../f': '//f', '/a/b/c/../d/e/../../../../../f': IndexError, - '/a/b/c/../d/e/../../../../f/..': ('/', ''), + '/a/b/c/../d/e/../../../../f/..': '//', + '/a/b/c/../d/e/../../../../f/../.': '//', } for path, expected in test_vectors.iteritems(): if isinstance(expected, type) and issubclass(expected, Exception): self.assertRaises(expected, - CGIHTTPServer._url_collapse_path_split, path) + CGIHTTPServer._url_collapse_path, path) else: - actual = CGIHTTPServer._url_collapse_path_split(path) + actual = CGIHTTPServer._url_collapse_path(path) self.assertEqual(expected, actual, msg='path = %r\nGot: %r\nWanted: %r' % (path, actual, expected)) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -634,6 +634,23 @@ "len(array.array) returns number of elements rather than bytelength" )(IOTest.test_array_writes) + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_invalid_operations(self): + pass + + # Jython does not use integer file descriptors but an object instead. + # Unfortunately, _pyio.open checks that it is an int. + # Override the affected test versions just so we can skip them visibly. + @unittest.skipIf(support.is_jython, "Jython does not use integer file descriptors") + def test_closefd_attr(self): + pass + @unittest.skipIf(support.is_jython, "Jython does not use integer file descriptors") + def test_read_closed(self): + pass + class CommonBufferedTests: # Tests common to BufferedReader, BufferedWriter and BufferedRandom @@ -1371,6 +1388,13 @@ class PyBufferedRWPairTest(BufferedRWPairTest): tp = pyio.BufferedRWPair + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_detach(self): + pass + class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest): read_mode = "rb+" @@ -2552,8 +2576,8 @@ self.assertEqual(g.raw.mode, "wb") self.assertEqual(g.name, f.fileno()) self.assertEqual(g.raw.name, f.fileno()) - f.close() - g.close() + g.close() # Jython difference: close g first (which may flush) ... + f.close() # Jython difference: then close f, which closes the fd def test_io_after_close(self): for kwargs in [ @@ -2719,10 +2743,23 @@ class PyMiscIOTest(MiscIOTest): io = pyio - - at unittest.skipIf(os.name == 'nt' or - (sys.platform[:4] == 'java' and os._name == 'nt'), - 'POSIX signals required for this test.') + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_io_after_close(self): + pass + + # Jython does not use integer file descriptors but an object instead. + # Unfortunately, _pyio.open checks that it is an int. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "Jython does not use integer file descriptors") + def test_attributes(self): + pass + + + at unittest.skipIf(support.is_jython, "Jython does not support os.pipe()") + at unittest.skipIf(os.name == 'nt', 'POSIX signals required for this test.') class SignalsTest(unittest.TestCase): def setUp(self): diff --git a/Lib/test/test_io_jy.py b/Lib/test/test_io_jy.py deleted file mode 100644 --- a/Lib/test/test_io_jy.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Misc io tests. - -Made for Jython. -""" -import unittest - -from org.python.core.util import FileUtil -from org.python.core.io import StreamIO - -from java.io import InputStream -from java.nio import ByteBuffer; - -class InfiniteInputStream(InputStream): - - def read(self, *args): - if len(args) == 0: - return ord('x') - elif len(args) == 1: - return InputStream.read(self, args[0]) - else: - return self.read_buffer(*args) - - def read_buffer(self, buf, off, length): - if length > 0: - buf[off] = ord('x') - return 1 - return 0 - - -class IoTestCase(unittest.TestCase): - """ - Jython was failing to read all available content when an InputStream - returns early. Java's InputStream.read() is allowed to return less than the - requested # of bytes under non-exceptional/EOF conditions, whereas - (for example) wsgi.input requires the file.read() method to block until the - requested # of bytes are available (except for exceptional/EOF conditions). - - See http://bugs.jython.org/issue1754 for more discussion. - """ - def test_infinite_input(self): - iis = InfiniteInputStream() - f = FileUtil.wrap(iis, 'rb') - size = 10000 - self.assertEqual(len(f.read(size)), size) - self.assertEqual(len(f.read(size)), size) - self.assertEqual(len(f.read(size)), size) - - def test_buffer_no_array(self): - """ - Directly tests StreamIO with and without a backing array and an - InputStream that returns early. - """ - size = 10000 - without_array = ByteBuffer.allocateDirect(size) - self.assertFalse(without_array.hasArray()) - with_array = ByteBuffer.allocate(size) - self.assertTrue(with_array.hasArray()) - bbs = [with_array, without_array] - for bb in bbs: - iis = InfiniteInputStream() - io = StreamIO(iis, True) - self.assertEqual(io.readinto(bb), size) - - -if __name__ == '__main__': - unittest.main() diff --git a/Lib/test/test_mailbox.py b/Lib/test/test_mailbox.py --- a/Lib/test/test_mailbox.py +++ b/Lib/test/test_mailbox.py @@ -8,6 +8,7 @@ import re import shutil import StringIO +import tempfile from test import test_support import unittest import mailbox @@ -20,7 +21,7 @@ # Silence Py3k warning rfc822 = test_support.import_module('rfc822', deprecated=True) -class TestBase(unittest.TestCase): +class TestBase: def _check_sample(self, msg): # Inspect a mailbox.Message representation of the sample message @@ -39,15 +40,15 @@ def _delete_recursively(self, target): # Delete a file or delete a directory recursively if os.path.isdir(target): - shutil.rmtree(target) + test_support.rmtree(target) elif os.path.exists(target): - os.remove(target) + test_support.unlink(target) class TestMailbox(TestBase): _factory = None # Overridden by subclasses to reuse tests - _template = 'From: foo\n\n%s' + _template = 'From: foo\n\n%s\n' def setUp(self): self._path = test_support.TESTFN @@ -75,6 +76,18 @@ for i in (1, 2, 3, 4): self._check_sample(self._box[keys[i]]) + def test_add_file(self): + with tempfile.TemporaryFile('w+') as f: + f.write(_sample_message) + f.seek(0) + key = self._box.add(f) + self.assertEqual(self._box.get_string(key).split('\n'), + _sample_message.split('\n')) + + def test_add_StringIO(self): + key = self._box.add(StringIO.StringIO(self._template % "0")) + self.assertEqual(self._box.get_string(key), self._template % "0") + def test_remove(self): # Remove messages using remove() self._test_remove_or_delitem(self._box.remove) @@ -124,7 +137,7 @@ key0 = self._box.add(self._template % 0) msg = self._box.get(key0) self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.get_payload(), '0') + self.assertEqual(msg.get_payload(), '0\n') self.assertIs(self._box.get('foo'), None) self.assertFalse(self._box.get('foo', False)) self._box.close() @@ -132,14 +145,15 @@ key1 = self._box.add(self._template % 1) msg = self._box.get(key1) self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.fp.read(), '1') + self.assertEqual(msg.fp.read(), '1' + os.linesep) + msg.fp.close() def test_getitem(self): # Retrieve message using __getitem__() key0 = self._box.add(self._template % 0) msg = self._box[key0] self.assertEqual(msg['from'], 'foo') - self.assertEqual(msg.get_payload(), '0') + self.assertEqual(msg.get_payload(), '0\n') self.assertRaises(KeyError, lambda: self._box['foo']) self._box.discard(key0) self.assertRaises(KeyError, lambda: self._box[key0]) @@ -151,7 +165,7 @@ msg0 = self._box.get_message(key0) self.assertIsInstance(msg0, mailbox.Message) self.assertEqual(msg0['from'], 'foo') - self.assertEqual(msg0.get_payload(), '0') + self.assertEqual(msg0.get_payload(), '0\n') self._check_sample(self._box.get_message(key1)) def test_get_string(self): @@ -165,10 +179,14 @@ # Get file representations of messages key0 = self._box.add(self._template % 0) key1 = self._box.add(_sample_message) - self.assertEqual(self._box.get_file(key0).read().replace(os.linesep, '\n'), + msg0 = self._box.get_file(key0) + self.assertEqual(msg0.read().replace(os.linesep, '\n'), self._template % 0) - self.assertEqual(self._box.get_file(key1).read().replace(os.linesep, '\n'), + msg1 = self._box.get_file(key1) + self.assertEqual(msg1.read().replace(os.linesep, '\n'), _sample_message) + msg0.close() + msg1.close() def test_get_file_can_be_closed_twice(self): # Issue 11700 @@ -320,15 +338,15 @@ self.assertIn(key0, self._box) key1 = self._box.add(self._template % 1) self.assertIn(key1, self._box) - self.assertEqual(self._box.pop(key0).get_payload(), '0') + self.assertEqual(self._box.pop(key0).get_payload(), '0\n') self.assertNotIn(key0, self._box) self.assertIn(key1, self._box) key2 = self._box.add(self._template % 2) self.assertIn(key2, self._box) - self.assertEqual(self._box.pop(key2).get_payload(), '2') + self.assertEqual(self._box.pop(key2).get_payload(), '2\n') self.assertNotIn(key2, self._box) self.assertIn(key1, self._box) - self.assertEqual(self._box.pop(key1).get_payload(), '1') + self.assertEqual(self._box.pop(key1).get_payload(), '1\n') self.assertNotIn(key1, self._box) self.assertEqual(len(self._box), 0) @@ -386,6 +404,17 @@ # Write changes to disk self._test_flush_or_close(self._box.flush, True) + def test_popitem_and_flush_twice(self): + # See #15036. + self._box.add(self._template % 0) + self._box.add(self._template % 1) + self._box.flush() + + self._box.popitem() + self._box.flush() + self._box.popitem() + self._box.flush() + def test_lock_unlock(self): # Lock and unlock the mailbox self.assertFalse(os.path.exists(self._get_lock_path())) @@ -403,6 +432,7 @@ self._box.add(contents[0]) self._box.add(contents[1]) self._box.add(contents[2]) + oldbox = self._box method() if should_call_close: self._box.close() @@ -411,6 +441,7 @@ self.assertEqual(len(keys), 3) for key in keys: self.assertIn(self._box.get_string(key), contents) + oldbox.close() def test_dump_message(self): # Write message representations to disk @@ -429,7 +460,7 @@ return self._path + '.lock' -class TestMailboxSuperclass(TestBase): +class TestMailboxSuperclass(TestBase, unittest.TestCase): def test_notimplemented(self): # Test that all Mailbox methods raise NotImplementedException. @@ -464,7 +495,7 @@ self.assertRaises(NotImplementedError, lambda: box.close()) -class TestMaildir(TestMailbox): +class TestMaildir(TestMailbox, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.Maildir(path, factory) @@ -506,7 +537,7 @@ msg_returned = self._box.get_message(key) self.assertEqual(msg_returned.get_subdir(), 'new') self.assertEqual(msg_returned.get_flags(), '') - self.assertEqual(msg_returned.get_payload(), '1') + self.assertEqual(msg_returned.get_payload(), '1\n') msg2 = mailbox.MaildirMessage(self._template % 2) msg2.set_info('2,S') self._box[key] = msg2 @@ -514,7 +545,7 @@ msg_returned = self._box.get_message(key) self.assertEqual(msg_returned.get_subdir(), 'new') self.assertEqual(msg_returned.get_flags(), 'S') - self.assertEqual(msg_returned.get_payload(), '3') + self.assertEqual(msg_returned.get_payload(), '3\n') def test_consistent_factory(self): # Add a message. @@ -636,13 +667,13 @@ self.assertTrue(match is not None, "Invalid file name: '%s'" % tail) groups = match.groups() if previous_groups is not None: - self.assertTrue(int(groups[0] >= previous_groups[0]), + self.assertGreaterEqual(int(groups[0]), int(previous_groups[0]), "Non-monotonic seconds: '%s' before '%s'" % (previous_groups[0], groups[0])) - self.assertTrue(int(groups[1] >= previous_groups[1]) or - groups[0] != groups[1], - "Non-monotonic milliseconds: '%s' before '%s'" % - (previous_groups[1], groups[1])) + if int(groups[0]) == int(previous_groups[0]): + self.assertGreaterEqual(int(groups[1]), int(previous_groups[1]), + "Non-monotonic milliseconds: '%s' before '%s'" % + (previous_groups[1], groups[1])) self.assertTrue(int(groups[2]) == pid, "Process ID mismatch: '%s' should be '%s'" % (groups[2], pid)) @@ -813,7 +844,49 @@ self._box._refresh() self.assertTrue(refreshed()) -class _TestMboxMMDF(TestMailbox): + +class _TestSingleFile(TestMailbox): + '''Common tests for single-file mailboxes''' + + def test_add_doesnt_rewrite(self): + # When only adding messages, flush() should not rewrite the + # mailbox file. See issue #9559. + + # Inode number changes if the contents are written to another + # file which is then renamed over the original file. So we + # must check that the inode number doesn't change. + inode_before = os.stat(self._path).st_ino + + self._box.add(self._template % 0) + self._box.flush() + + inode_after = os.stat(self._path).st_ino + self.assertEqual(inode_before, inode_after) + + # Make sure the message was really added + self._box.close() + self._box = self._factory(self._path) + self.assertEqual(len(self._box), 1) + + def test_permissions_after_flush(self): + # See issue #5346 + + # Make the mailbox world writable. It's unlikely that the new + # mailbox file would have these permissions after flush(), + # because umask usually prevents it. + mode = os.stat(self._path).st_mode | 0o666 + os.chmod(self._path, mode) + + self._box.add(self._template % 0) + i = self._box.add(self._template % 1) + # Need to remove one message to make flush() create a new file + self._box.remove(i) + self._box.flush() + + self.assertEqual(os.stat(self._path).st_mode, mode) + + +class _TestMboxMMDF(_TestSingleFile): def tearDown(self): self._box.close() @@ -823,14 +896,14 @@ def test_add_from_string(self): # Add a string starting with 'From ' to the mailbox - key = self._box.add('From foo at bar blah\nFrom: foo\n\n0') + key = self._box.add('From foo at bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo at bar blah') - self.assertEqual(self._box[key].get_payload(), '0') + self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_mbox_or_mmdf_message(self): # Add an mboxMessage or MMDFMessage for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): - msg = class_('From foo at bar blah\nFrom: foo\n\n0') + msg = class_('From foo at bar blah\nFrom: foo\n\n0\n') key = self._box.add(msg) def test_open_close_open(self): @@ -914,7 +987,7 @@ self._box.close() -class TestMbox(_TestMboxMMDF): +class TestMbox(_TestMboxMMDF, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.mbox(path, factory) @@ -937,12 +1010,35 @@ perms = st.st_mode self.assertFalse((perms & 0111)) # Execute bits should all be off. -class TestMMDF(_TestMboxMMDF): + def test_terminating_newline(self): + message = email.message.Message() + message['From'] = 'john at example.com' + message.set_payload('No newline at the end') + i = self._box.add(message) + + # A newline should have been appended to the payload + message = self._box.get(i) + self.assertEqual(message.get_payload(), 'No newline at the end\n') + + def test_message_separator(self): + # Check there's always a single blank line after each message + self._box.add('From: foo\n\n0') # No newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + self._box.add('From: foo\n\n0\n') # Newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + +class TestMMDF(_TestMboxMMDF, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.MMDF(path, factory) -class TestMH(TestMailbox): +class TestMH(TestMailbox, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.MH(path, factory) @@ -1074,7 +1170,7 @@ return os.path.join(self._path, '.mh_sequences.lock') -class TestBabyl(TestMailbox): +class TestBabyl(_TestSingleFile, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.Babyl(path, factory) @@ -1103,7 +1199,7 @@ self.assertEqual(set(self._box.get_labels()), set(['blah'])) -class TestMessage(TestBase): +class TestMessage(TestBase, unittest.TestCase): _factory = mailbox.Message # Overridden by subclasses to reuse tests @@ -1174,7 +1270,7 @@ pass -class TestMaildirMessage(TestMessage): +class TestMaildirMessage(TestMessage, unittest.TestCase): _factory = mailbox.MaildirMessage @@ -1249,7 +1345,7 @@ self._check_sample(msg) -class _TestMboxMMDFMessage(TestMessage): +class _TestMboxMMDFMessage: _factory = mailbox._mboxMMDFMessage @@ -1296,12 +1392,12 @@ r"\d{2} \d{4}", msg.get_from())) -class TestMboxMessage(_TestMboxMMDFMessage): +class TestMboxMessage(_TestMboxMMDFMessage, TestMessage): _factory = mailbox.mboxMessage -class TestMHMessage(TestMessage): +class TestMHMessage(TestMessage, unittest.TestCase): _factory = mailbox.MHMessage @@ -1332,7 +1428,7 @@ self.assertEqual(msg.get_sequences(), ['foobar', 'replied']) -class TestBabylMessage(TestMessage): +class TestBabylMessage(TestMessage, unittest.TestCase): _factory = mailbox.BabylMessage @@ -1387,12 +1483,12 @@ self.assertEqual(visible[header], msg[header]) -class TestMMDFMessage(_TestMboxMMDFMessage): +class TestMMDFMessage(_TestMboxMMDFMessage, TestMessage): _factory = mailbox.MMDFMessage -class TestMessageConversion(TestBase): +class TestMessageConversion(TestBase, unittest.TestCase): def test_plain_to_x(self): # Convert Message to all formats @@ -1715,7 +1811,7 @@ proxy.close() -class TestProxyFile(TestProxyFileBase): +class TestProxyFile(TestProxyFileBase, unittest.TestCase): def setUp(self): self._path = test_support.TESTFN @@ -1764,7 +1860,7 @@ self._test_close(mailbox._ProxyFile(self._file)) -class TestPartialFile(TestProxyFileBase): +class TestPartialFile(TestProxyFileBase, unittest.TestCase): def setUp(self): self._path = test_support.TESTFN @@ -1831,6 +1927,10 @@ def setUp(self): # create a new maildir mailbox to work with: self._dir = test_support.TESTFN + if os.path.isdir(self._dir): + test_support.rmtree(self._dir) + if os.path.isfile(self._dir): + test_support.unlink(self._dir) os.mkdir(self._dir) os.mkdir(os.path.join(self._dir, "cur")) os.mkdir(os.path.join(self._dir, "tmp")) @@ -1840,10 +1940,10 @@ def tearDown(self): map(os.unlink, self._msgfiles) - os.rmdir(os.path.join(self._dir, "cur")) - os.rmdir(os.path.join(self._dir, "tmp")) - os.rmdir(os.path.join(self._dir, "new")) - os.rmdir(self._dir) + test_support.rmdir(os.path.join(self._dir, "cur")) + test_support.rmdir(os.path.join(self._dir, "tmp")) + test_support.rmdir(os.path.join(self._dir, "new")) + test_support.rmdir(self._dir) def createMessage(self, dir, mbox=False): t = int(time.time() % 1000000) @@ -1879,7 +1979,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1887,7 +1989,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1896,8 +2000,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 2) - self.assertIsNot(self.mbox.next(), None) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1906,11 +2014,13 @@ import email.parser fname = self.createMessage("cur", True) n = 0 - for msg in mailbox.PortableUnixMailbox(open(fname), + fid = open(fname) + for msg in mailbox.PortableUnixMailbox(fid, email.parser.Parser().parse): n += 1 self.assertEqual(msg["subject"], "Simple Test") self.assertEqual(len(str(msg)), len(FROM_)+len(DUMMY_MESSAGE)) + fid.close() self.assertEqual(n, 1) ## End: classes from the original module (for backward compatibility). diff --git a/Lib/test/test_memoryio.py b/Lib/test/test_memoryio.py new file mode 100644 --- /dev/null +++ b/Lib/test/test_memoryio.py @@ -0,0 +1,770 @@ +"""Unit tests for memory-based file-like objects. +StringIO -- for unicode strings +BytesIO -- for bytes +""" + +from __future__ import unicode_literals +from __future__ import print_function + +import unittest +from test import test_support as support + +import io +import _pyio as pyio +import pickle + +class MemorySeekTestMixin: + + def testInit(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + def testRead(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + self.assertEqual(buf[:1], bytesIo.read(1)) + self.assertEqual(buf[1:5], bytesIo.read(4)) + self.assertEqual(buf[5:], bytesIo.read(900)) + self.assertEqual(self.EOF, bytesIo.read()) + + def testReadNoArgs(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + self.assertEqual(buf, bytesIo.read()) + self.assertEqual(self.EOF, bytesIo.read()) + + def testSeek(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + bytesIo.read(5) + bytesIo.seek(0) + self.assertEqual(buf, bytesIo.read()) + + bytesIo.seek(3) + self.assertEqual(buf[3:], bytesIo.read()) + self.assertRaises(TypeError, bytesIo.seek, 0.0) + + def testTell(self): + buf = self.buftype("1234567890") + bytesIo = self.ioclass(buf) + + self.assertEqual(0, bytesIo.tell()) + bytesIo.seek(5) + self.assertEqual(5, bytesIo.tell()) + bytesIo.seek(10000) + self.assertEqual(10000, bytesIo.tell()) + + +class MemoryTestMixin: + + def test_detach(self): + buf = self.ioclass() + self.assertRaises(self.UnsupportedOperation, buf.detach) + + def write_ops(self, f, t): + self.assertEqual(f.write(t("blah.")), 5) + self.assertEqual(f.seek(0), 0) + self.assertEqual(f.write(t("Hello.")), 6) + self.assertEqual(f.tell(), 6) + self.assertEqual(f.seek(5), 5) + self.assertEqual(f.tell(), 5) + self.assertEqual(f.write(t(" world\n\n\n")), 9) + self.assertEqual(f.seek(0), 0) + self.assertEqual(f.write(t("h")), 1) + self.assertEqual(f.truncate(12), 12) + self.assertEqual(f.tell(), 1) + + def test_write(self): + buf = self.buftype("hello world\n") + memio = self.ioclass(buf) + + self.write_ops(memio, self.buftype) + self.assertEqual(memio.getvalue(), buf) + memio = self.ioclass() + self.write_ops(memio, self.buftype) + self.assertEqual(memio.getvalue(), buf) + self.assertRaises(TypeError, memio.write, None) + memio.close() + self.assertRaises(ValueError, memio.write, self.buftype("")) + + def test_writelines(self): + buf = self.buftype("1234567890") + memio = self.ioclass() + + self.assertEqual(memio.writelines([buf] * 100), None) + self.assertEqual(memio.getvalue(), buf * 100) + memio.writelines([]) + self.assertEqual(memio.getvalue(), buf * 100) + memio = self.ioclass() + self.assertRaises(TypeError, memio.writelines, [buf] + [1]) + self.assertEqual(memio.getvalue(), buf) + self.assertRaises(TypeError, memio.writelines, None) + memio.close() + self.assertRaises(ValueError, memio.writelines, []) + + def test_writelines_error(self): + memio = self.ioclass() + def error_gen(): + yield self.buftype('spam') + raise KeyboardInterrupt + + self.assertRaises(KeyboardInterrupt, memio.writelines, error_gen()) + + def test_truncate(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertRaises(ValueError, memio.truncate, -1) + memio.seek(6) + self.assertEqual(memio.truncate(), 6) + self.assertEqual(memio.getvalue(), buf[:6]) + self.assertEqual(memio.truncate(4), 4) + self.assertEqual(memio.getvalue(), buf[:4]) + # truncate() accepts long objects + self.assertEqual(memio.truncate(4L), 4) + self.assertEqual(memio.getvalue(), buf[:4]) + self.assertEqual(memio.tell(), 6) + memio.seek(0, 2) + memio.write(buf) + self.assertEqual(memio.getvalue(), buf[:4] + buf) + pos = memio.tell() + self.assertEqual(memio.truncate(None), pos) + self.assertEqual(memio.tell(), pos) + self.assertRaises(TypeError, memio.truncate, '0') + memio.close() + self.assertRaises(ValueError, memio.truncate, 0) + + def test_init(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + self.assertEqual(memio.getvalue(), buf) + memio = self.ioclass(None) + self.assertEqual(memio.getvalue(), self.EOF) + memio.__init__(buf * 2) + self.assertEqual(memio.getvalue(), buf * 2) + memio.__init__(buf) + self.assertEqual(memio.getvalue(), buf) + + def test_read(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.read(0), self.EOF) + self.assertEqual(memio.read(1), buf[:1]) + # read() accepts long objects + self.assertEqual(memio.read(4L), buf[1:5]) + self.assertEqual(memio.read(900), buf[5:]) + self.assertEqual(memio.read(), self.EOF) + memio.seek(0) + self.assertEqual(memio.read(), buf) + self.assertEqual(memio.read(), self.EOF) + self.assertEqual(memio.tell(), 10) + memio.seek(0) + self.assertEqual(memio.read(-1), buf) + memio.seek(0) + self.assertEqual(type(memio.read()), type(buf)) + memio.seek(100) + self.assertEqual(type(memio.read()), type(buf)) + memio.seek(0) + self.assertEqual(memio.read(None), buf) + self.assertRaises(TypeError, memio.read, '') + memio.close() + self.assertRaises(ValueError, memio.read) + + def test_readline(self): + buf = self.buftype("1234567890\n") + memio = self.ioclass(buf * 2) + + self.assertEqual(memio.readline(0), self.EOF) + self.assertEqual(memio.readline(), buf) + self.assertEqual(memio.readline(), buf) + self.assertEqual(memio.readline(), self.EOF) + memio.seek(0) + self.assertEqual(memio.readline(5), buf[:5]) + # readline() accepts long objects + self.assertEqual(memio.readline(5L), buf[5:10]) + self.assertEqual(memio.readline(5), buf[10:15]) + memio.seek(0) + self.assertEqual(memio.readline(-1), buf) + memio.seek(0) + self.assertEqual(memio.readline(0), self.EOF) + + buf = self.buftype("1234567890\n") + memio = self.ioclass((buf * 3)[:-1]) + self.assertEqual(memio.readline(), buf) + self.assertEqual(memio.readline(), buf) + self.assertEqual(memio.readline(), buf[:-1]) + self.assertEqual(memio.readline(), self.EOF) + memio.seek(0) + self.assertEqual(type(memio.readline()), type(buf)) + self.assertEqual(memio.readline(), buf) + self.assertRaises(TypeError, memio.readline, '') + memio.close() + self.assertRaises(ValueError, memio.readline) + + def test_readlines(self): + buf = self.buftype("1234567890\n") + memio = self.ioclass(buf * 10) + + self.assertEqual(memio.readlines(), [buf] * 10) + memio.seek(5) + self.assertEqual(memio.readlines(), [buf[5:]] + [buf] * 9) + memio.seek(0) + # readlines() accepts long objects + self.assertEqual(memio.readlines(15L), [buf] * 2) + memio.seek(0) + self.assertEqual(memio.readlines(-1), [buf] * 10) + memio.seek(0) + self.assertEqual(memio.readlines(0), [buf] * 10) + memio.seek(0) + self.assertEqual(type(memio.readlines()[0]), type(buf)) + memio.seek(0) + self.assertEqual(memio.readlines(None), [buf] * 10) + self.assertRaises(TypeError, memio.readlines, '') + memio.close() + self.assertRaises(ValueError, memio.readlines) + + def test_iterator(self): + buf = self.buftype("1234567890\n") + memio = self.ioclass(buf * 10) + + self.assertEqual(iter(memio), memio) + self.assertTrue(hasattr(memio, '__iter__')) + self.assertTrue(hasattr(memio, 'next')) + i = 0 + for line in memio: + self.assertEqual(line, buf) + i += 1 + self.assertEqual(i, 10) + memio.seek(0) + i = 0 + for line in memio: + self.assertEqual(line, buf) + i += 1 + self.assertEqual(i, 10) + memio = self.ioclass(buf * 2) + memio.close() + self.assertRaises(ValueError, next, memio) + + def test_getvalue(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.getvalue(), buf) + memio.read() + self.assertEqual(memio.getvalue(), buf) + self.assertEqual(type(memio.getvalue()), type(buf)) + memio = self.ioclass(buf * 1000) + self.assertEqual(memio.getvalue()[-3:], self.buftype("890")) + memio = self.ioclass(buf) + memio.close() + self.assertRaises(ValueError, memio.getvalue) + + def test_seek(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + memio.read(5) + self.assertRaises(ValueError, memio.seek, -1) + self.assertRaises(ValueError, memio.seek, 1, -1) + self.assertRaises(ValueError, memio.seek, 1, 3) + self.assertEqual(memio.seek(0), 0) + self.assertEqual(memio.seek(0, 0), 0) + self.assertEqual(memio.read(), buf) + self.assertEqual(memio.seek(3), 3) + # seek() accepts long objects + self.assertEqual(memio.seek(3L), 3) + self.assertEqual(memio.seek(0, 1), 3) + self.assertEqual(memio.read(), buf[3:]) + self.assertEqual(memio.seek(len(buf)), len(buf)) + self.assertEqual(memio.read(), self.EOF) + memio.seek(len(buf) + 1) + self.assertEqual(memio.read(), self.EOF) + self.assertEqual(memio.seek(0, 2), len(buf)) + self.assertEqual(memio.read(), self.EOF) + memio.close() + self.assertRaises(ValueError, memio.seek, 0) + + def test_overseek(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.seek(len(buf) + 1), 11) + self.assertEqual(memio.read(), self.EOF) + self.assertEqual(memio.tell(), 11) + self.assertEqual(memio.getvalue(), buf) + memio.write(self.EOF) + self.assertEqual(memio.getvalue(), buf) + memio.write(buf) + self.assertEqual(memio.getvalue(), buf + self.buftype('\0') + buf) + + def test_tell(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.tell(), 0) + memio.seek(5) + self.assertEqual(memio.tell(), 5) + memio.seek(10000) + self.assertEqual(memio.tell(), 10000) + memio.close() + self.assertRaises(ValueError, memio.tell) + + def test_flush(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.flush(), None) + + def test_flags(self): + memio = self.ioclass() + + self.assertEqual(memio.writable(), True) + self.assertEqual(memio.readable(), True) + self.assertEqual(memio.seekable(), True) + self.assertEqual(memio.isatty(), False) + self.assertEqual(memio.closed, False) + memio.close() + self.assertRaises(ValueError, memio.writable) + self.assertRaises(ValueError, memio.readable) + self.assertRaises(ValueError, memio.seekable) + self.assertRaises(ValueError, memio.isatty) + self.assertEqual(memio.closed, True) + + def test_subclassing(self): + buf = self.buftype("1234567890") + def test1(): + class MemIO(self.ioclass): + pass + m = MemIO(buf) + return m.getvalue() + def test2(): + class MemIO(self.ioclass): + def __init__(me, a, b): + self.ioclass.__init__(me, a) + m = MemIO(buf, None) + return m.getvalue() + self.assertEqual(test1(), buf) + self.assertEqual(test2(), buf) + + def test_instance_dict_leak(self): + # Test case for issue #6242. + # This will be caught by regrtest.py -R if this leak. + for _ in range(100): + memio = self.ioclass() + memio.foo = 1 + + def test_pickling(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + memio.foo = 42 + memio.seek(2) + + class PickleTestMemIO(self.ioclass): + def __init__(me, initvalue, foo): + self.ioclass.__init__(me, initvalue) + me.foo = foo + # __getnewargs__ is undefined on purpose. This checks that PEP 307 + # is used to provide pickling support. + + # Pickle expects the class to be on the module level. Here we use a + # little hack to allow the PickleTestMemIO class to derive from + # self.ioclass without having to define all combinations explicitly on + # the module-level. + import __main__ + PickleTestMemIO.__module__ = '__main__' + __main__.PickleTestMemIO = PickleTestMemIO + submemio = PickleTestMemIO(buf, 80) + submemio.seek(2) + + # We only support pickle protocol 2 and onward since we use extended + # __reduce__ API of PEP 307 to provide pickling support. + for proto in range(2, pickle.HIGHEST_PROTOCOL): + for obj in (memio, submemio): + obj2 = pickle.loads(pickle.dumps(obj, protocol=proto)) + self.assertEqual(obj.getvalue(), obj2.getvalue()) + self.assertEqual(obj.__class__, obj2.__class__) + self.assertEqual(obj.foo, obj2.foo) + self.assertEqual(obj.tell(), obj2.tell()) + obj.close() + self.assertRaises(ValueError, pickle.dumps, obj, proto) + del __main__.PickleTestMemIO + + +class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase): + + UnsupportedOperation = pyio.UnsupportedOperation + + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_detach(self): + pass + + @staticmethod + def buftype(s): + return s.encode("ascii") + ioclass = pyio.BytesIO + EOF = b"" + + def test_read1(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertRaises(TypeError, memio.read1) + self.assertEqual(memio.read(), buf) + + def test_readinto(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + b = bytearray(b"hello") + self.assertEqual(memio.readinto(b), 5) + self.assertEqual(b, b"12345") + self.assertEqual(memio.readinto(b), 5) + self.assertEqual(b, b"67890") + self.assertEqual(memio.readinto(b), 0) + self.assertEqual(b, b"67890") + b = bytearray(b"hello world") + memio.seek(0) + self.assertEqual(memio.readinto(b), 10) + self.assertEqual(b, b"1234567890d") + b = bytearray(b"") + memio.seek(0) + self.assertEqual(memio.readinto(b), 0) + self.assertEqual(b, b"") + self.assertRaises(TypeError, memio.readinto, '') + import array + a = array.array(b'b', b"hello world") + memio = self.ioclass(buf) + memio.readinto(a) + self.assertEqual(a.tostring(), b"1234567890d") + memio.close() + self.assertRaises(ValueError, memio.readinto, b) + memio = self.ioclass(b"123") + b = bytearray() + memio.seek(42) + memio.readinto(b) + self.assertEqual(b, b"") + + def test_relative_seek(self): + buf = self.buftype("1234567890") + memio = self.ioclass(buf) + + self.assertEqual(memio.seek(-1, 1), 0) + self.assertEqual(memio.seek(3, 1), 3) + self.assertEqual(memio.seek(-4, 1), 0) + self.assertEqual(memio.seek(-1, 2), 9) + self.assertEqual(memio.seek(1, 1), 10) + self.assertEqual(memio.seek(1, 2), 11) + memio.seek(-3, 2) + self.assertEqual(memio.read(), buf[-3:]) + memio.seek(0) + memio.seek(1, 1) + self.assertEqual(memio.read(), buf[1:]) + + def test_unicode(self): + memio = self.ioclass() + + self.assertRaises(TypeError, self.ioclass, "1234567890") + self.assertRaises(TypeError, memio.write, "1234567890") + self.assertRaises(TypeError, memio.writelines, ["1234567890"]) + + def test_bytes_array(self): + buf = b"1234567890" + import array + a = array.array(b'b', buf) + memio = self.ioclass(a) + self.assertEqual(memio.getvalue(), buf) + self.assertEqual(memio.write(a), 10) + self.assertEqual(memio.getvalue(), buf) + + def test_issue5449(self): + buf = self.buftype("1234567890") + self.ioclass(initial_bytes=buf) + self.assertRaises(TypeError, self.ioclass, buf, foo=None) + + +class TextIOTestMixin: + + def test_newlines_property(self): + memio = self.ioclass(newline=None) + # The C StringIO decodes newlines in write() calls, but the Python + # implementation only does when reading. This function forces them to + # be decoded for testing. + def force_decode(): + memio.seek(0) + memio.read() + self.assertEqual(memio.newlines, None) + memio.write("a\n") + force_decode() + self.assertEqual(memio.newlines, "\n") + memio.write("b\r\n") + force_decode() + self.assertEqual(memio.newlines, ("\n", "\r\n")) + memio.write("c\rd") + force_decode() + self.assertEqual(memio.newlines, ("\r", "\n", "\r\n")) + + def test_relative_seek(self): + memio = self.ioclass() + + self.assertRaises(IOError, memio.seek, -1, 1) + self.assertRaises(IOError, memio.seek, 3, 1) + self.assertRaises(IOError, memio.seek, -3, 1) + self.assertRaises(IOError, memio.seek, -1, 2) + self.assertRaises(IOError, memio.seek, 1, 1) + self.assertRaises(IOError, memio.seek, 1, 2) + + def test_textio_properties(self): + memio = self.ioclass() + + # These are just dummy values but we nevertheless check them for fear + # of unexpected breakage. + self.assertIsNone(memio.encoding) + self.assertIsNone(memio.errors) + self.assertFalse(memio.line_buffering) + + def test_newline_none(self): + # newline=None + memio = self.ioclass("a\nb\r\nc\rd", newline=None) + self.assertEqual(list(memio), ["a\n", "b\n", "c\n", "d"]) + memio.seek(0) + self.assertEqual(memio.read(1), "a") + self.assertEqual(memio.read(2), "\nb") + self.assertEqual(memio.read(2), "\nc") + self.assertEqual(memio.read(1), "\n") + memio = self.ioclass(newline=None) + self.assertEqual(2, memio.write("a\n")) + self.assertEqual(3, memio.write("b\r\n")) + self.assertEqual(3, memio.write("c\rd")) + memio.seek(0) + self.assertEqual(memio.read(), "a\nb\nc\nd") + memio = self.ioclass("a\r\nb", newline=None) + self.assertEqual(memio.read(3), "a\nb") + + def test_newline_empty(self): + # newline="" + memio = self.ioclass("a\nb\r\nc\rd", newline="") + self.assertEqual(list(memio), ["a\n", "b\r\n", "c\r", "d"]) + memio.seek(0) + self.assertEqual(memio.read(4), "a\nb\r") + self.assertEqual(memio.read(2), "\nc") + self.assertEqual(memio.read(1), "\r") + memio = self.ioclass(newline="") + self.assertEqual(2, memio.write("a\n")) + self.assertEqual(2, memio.write("b\r")) + self.assertEqual(2, memio.write("\nc")) + self.assertEqual(2, memio.write("\rd")) + memio.seek(0) + self.assertEqual(list(memio), ["a\n", "b\r\n", "c\r", "d"]) + + def test_newline_lf(self): + # newline="\n" + memio = self.ioclass("a\nb\r\nc\rd") + self.assertEqual(list(memio), ["a\n", "b\r\n", "c\rd"]) + + def test_newline_cr(self): + # newline="\r" + memio = self.ioclass("a\nb\r\nc\rd", newline="\r") + self.assertEqual(memio.read(), "a\rb\r\rc\rd") + memio.seek(0) + self.assertEqual(list(memio), ["a\r", "b\r", "\r", "c\r", "d"]) + + def test_newline_crlf(self): + # newline="\r\n" + memio = self.ioclass("a\nb\r\nc\rd", newline="\r\n") + self.assertEqual(memio.read(), "a\r\nb\r\r\nc\rd") + memio.seek(0) + self.assertEqual(list(memio), ["a\r\n", "b\r\r\n", "c\rd"]) + + def test_issue5265(self): + # StringIO can duplicate newlines in universal newlines mode + memio = self.ioclass("a\r\nb\r\n", newline=None) + self.assertEqual(memio.read(5), "a\nb\n") + + +class PyStringIOTest(MemoryTestMixin, MemorySeekTestMixin, + TextIOTestMixin, unittest.TestCase): + buftype = unicode + ioclass = pyio.StringIO + UnsupportedOperation = pyio.UnsupportedOperation + EOF = "" + + # When Jython tries to use UnsupportedOperation as _pyio defines it, it runs + # into a problem with multiple inheritance and the slots array: issue 1996. + # Override the affected test version just so we can skip it visibly. + @unittest.skipIf(support.is_jython, "FIXME: Jython issue 1996") + def test_detach(self): + pass + + +class PyStringIOPickleTest(TextIOTestMixin, unittest.TestCase): + """Test if pickle restores properly the internal state of StringIO. + """ + buftype = unicode + UnsupportedOperation = pyio.UnsupportedOperation + EOF = "" + + class ioclass(pyio.StringIO): + def __new__(cls, *args, **kwargs): + return pickle.loads(pickle.dumps(pyio.StringIO(*args, **kwargs))) + def __init__(self, *args, **kwargs): + pass + + +class CBytesIOTest(PyBytesIOTest): + ioclass = io.BytesIO + UnsupportedOperation = io.UnsupportedOperation + + test_bytes_array = unittest.skip( + "array.array() does not have the new buffer API" + )(PyBytesIOTest.test_bytes_array) + + # Re-instate test_detach skipped by Jython in PyBytesIOTest + if support.is_jython: # FIXME: Jython issue 1996 + test_detach = MemoryTestMixin.test_detach + + def test_getstate(self): + memio = self.ioclass() + state = memio.__getstate__() + self.assertEqual(len(state), 3) + bytearray(state[0]) # Check if state[0] supports the buffer interface. + self.assertIsInstance(state[1], int) + self.assertTrue(isinstance(state[2], dict) or state[2] is None) + memio.close() + self.assertRaises(ValueError, memio.__getstate__) + + def test_setstate(self): + # This checks whether __setstate__ does proper input validation. + memio = self.ioclass() + memio.__setstate__((b"no error", 0, None)) + memio.__setstate__((bytearray(b"no error"), 0, None)) + memio.__setstate__((b"no error", 0, {'spam': 3})) + self.assertRaises(ValueError, memio.__setstate__, (b"", -1, None)) + self.assertRaises(TypeError, memio.__setstate__, ("unicode", 0, None)) + self.assertRaises(TypeError, memio.__setstate__, (b"", 0.0, None)) + self.assertRaises(TypeError, memio.__setstate__, (b"", 0, 0)) + self.assertRaises(TypeError, memio.__setstate__, (b"len-test", 0)) + self.assertRaises(TypeError, memio.__setstate__) + self.assertRaises(TypeError, memio.__setstate__, 0) + memio.close() + self.assertRaises(ValueError, memio.__setstate__, (b"closed", 0, None)) + + check_sizeof = support.check_sizeof + + @support.cpython_only + def test_sizeof(self): + basesize = support.calcobjsize(b'P2PP2P') + check = self.check_sizeof + self.assertEqual(object.__sizeof__(io.BytesIO()), basesize) + check(io.BytesIO(), basesize ) + check(io.BytesIO(b'a'), basesize + 1 + 1 ) + check(io.BytesIO(b'a' * 1000), basesize + 1000 + 1 ) + +class CStringIOTest(PyStringIOTest): + ioclass = io.StringIO + UnsupportedOperation = io.UnsupportedOperation + + # XXX: For the Python version of io.StringIO, this is highly + # dependent on the encoding used for the underlying buffer. + + # Re-instate test_detach skipped by Jython in PyBytesIOTest + if support.is_jython: # FIXME: Jython issue 1996 + test_detach = MemoryTestMixin.test_detach + + # This test checks that tell() results are consistent with the length of + # text written, but this is not documented in the API: only that seek() + # accept what tell() returns. + @unittest.skipIf(support.is_jython, "Exact value of tell() is CPython specific") + def test_widechar(self): + buf = self.buftype("\U0002030a\U00020347") + memio = self.ioclass(buf) + + self.assertEqual(memio.getvalue(), buf) + self.assertEqual(memio.write(buf), len(buf)) + self.assertEqual(memio.tell(), len(buf)) + self.assertEqual(memio.getvalue(), buf) + self.assertEqual(memio.write(buf), len(buf)) + self.assertEqual(memio.tell(), len(buf) * 2) + self.assertEqual(memio.getvalue(), buf + buf) + + # This test checks that seek() accepts what tell() returns, without requiring + # that tell() return a particular absolute value. Conceived for Jython, but + # probably universal. + def test_widechar_seek(self): + buf = self.buftype("\U0002030aX\u00ca\U00020347\u05d1Y\u0628Z") + memio = self.ioclass(buf) + self.assertEqual(memio.getvalue(), buf) + + # For each character in buf, read it back from memio and its tell value + chars = list(buf) + tells = list() + for ch in chars : + tells.append(memio.tell()) + self.assertEqual(memio.read(1), ch) + + # For each character in buf, seek to it and check it's there + chpos = zip(chars, tells) + chpos.reverse() + for ch, pos in chpos: + memio.seek(pos) + self.assertEqual(memio.read(1), ch) + + # Check write after seek to end + memio.seek(0, 2) + self.assertEqual(memio.write(buf), len(buf)) + self.assertEqual(memio.getvalue(), buf + buf) + + def test_getstate(self): + memio = self.ioclass() + state = memio.__getstate__() + self.assertEqual(len(state), 4) + self.assertIsInstance(state[0], unicode) + self.assertIsInstance(state[1], str) + self.assertIsInstance(state[2], int) + self.assertTrue(isinstance(state[3], dict) or state[3] is None) + memio.close() + self.assertRaises(ValueError, memio.__getstate__) + + def test_setstate(self): + # This checks whether __setstate__ does proper input validation. + memio = self.ioclass() + memio.__setstate__(("no error", "\n", 0, None)) + memio.__setstate__(("no error", "", 0, {'spam': 3})) + self.assertRaises(ValueError, memio.__setstate__, ("", "f", 0, None)) + self.assertRaises(ValueError, memio.__setstate__, ("", "", -1, None)) + self.assertRaises(TypeError, memio.__setstate__, (b"", "", 0, None)) + # trunk is more tolerant than py3k on the type of the newline param + #self.assertRaises(TypeError, memio.__setstate__, ("", b"", 0, None)) + self.assertRaises(TypeError, memio.__setstate__, ("", "", 0.0, None)) + self.assertRaises(TypeError, memio.__setstate__, ("", "", 0, 0)) + self.assertRaises(TypeError, memio.__setstate__, ("len-test", 0)) + self.assertRaises(TypeError, memio.__setstate__) + self.assertRaises(TypeError, memio.__setstate__, 0) + memio.close() + self.assertRaises(ValueError, memio.__setstate__, ("closed", "", 0, None)) + + +class CStringIOPickleTest(PyStringIOPickleTest): + UnsupportedOperation = io.UnsupportedOperation + + class ioclass(io.StringIO): + def __new__(cls, *args, **kwargs): + return pickle.loads(pickle.dumps(io.StringIO(*args, **kwargs), + protocol=2)) + def __init__(self, *args, **kwargs): + pass + + +def test_main(): + tests = [PyBytesIOTest, PyStringIOTest, CBytesIOTest, CStringIOTest, + PyStringIOPickleTest, CStringIOPickleTest] + support.run_unittest(*tests) + +if __name__ == '__main__': + test_main() diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -224,6 +224,8 @@ res = platform.dist() def test_libc_ver(self): + if sys.executable is None: + return import os if os.path.isdir(sys.executable) and \ os.path.exists(sys.executable+'.exe'): diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -196,7 +196,6 @@ env=env) self.assertEqual(rc, 1) - @unittest.skipIf(is_jython, "FIXME: not on Jython yet.") def test_getuserbase(self): site.USER_BASE = None user_base = site.getuserbase() diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -18,6 +18,14 @@ import UserDict import re import time +import struct +import sysconfig + +try: + import _testcapi +except ImportError: + _testcapi = None + try: import thread except ImportError: @@ -181,15 +189,79 @@ except KeyError: pass +if sys.platform.startswith("win"): + def _waitfor(func, pathname, waitall=False): + # Peform the operation + func(pathname) + # Now setup the wait loop + if waitall: + dirname = pathname + else: + dirname, name = os.path.split(pathname) + dirname = dirname or '.' + # Check for `pathname` to be removed from the filesystem. + # The exponential backoff of the timeout amounts to a total + # of ~1 second after which the deletion is probably an error + # anyway. + # Testing on a i7 at 4.3GHz shows that usually only 1 iteration is + # required when contention occurs. + timeout = 0.001 + while timeout < 1.0: + # Note we are only testing for the existance of the file(s) in + # the contents of the directory regardless of any security or + # access rights. If we have made it this far, we have sufficient + # permissions to do that much using Python's equivalent of the + # Windows API FindFirstFile. + # Other Windows APIs can fail or give incorrect results when + # dealing with files that are pending deletion. + L = os.listdir(dirname) + if not (L if waitall else name in L): + return + # Increase the timeout and try again + time.sleep(timeout) + timeout *= 2 + warnings.warn('tests may fail, delete still pending for ' + pathname, + RuntimeWarning, stacklevel=4) + + def _unlink(filename): + _waitfor(os.unlink, filename) + + def _rmdir(dirname): + _waitfor(os.rmdir, dirname) + + def _rmtree(path): + def _rmtree_inner(path): + for name in os.listdir(path): + fullname = os.path.join(path, name) + if os.path.isdir(fullname): + _waitfor(_rmtree_inner, fullname, waitall=True) + os.rmdir(fullname) + else: + os.unlink(fullname) + _waitfor(_rmtree_inner, path, waitall=True) + _waitfor(os.rmdir, path) +else: + _unlink = os.unlink + _rmdir = os.rmdir + _rmtree = shutil.rmtree + def unlink(filename): try: - os.unlink(filename) + _unlink(filename) except OSError: pass +def rmdir(dirname): + try: + _rmdir(dirname) + except OSError as error: + # The directory need not exist. + if error.errno != errno.ENOENT: + raise + def rmtree(path): try: - shutil.rmtree(path) + _rmtree(path) except OSError, e: # Unix returns ENOENT, Windows returns ESRCH. if e.errno not in (errno.ENOENT, errno.ESRCH): @@ -465,7 +537,7 @@ the CWD, an error is raised. If it's True, only a warning is raised and the original CWD is used. """ - if isinstance(name, unicode): + if have_unicode and isinstance(name, unicode): try: name = name.encode(sys.getfilesystemencoding() or 'ascii') except UnicodeEncodeError: @@ -813,6 +885,9 @@ ('EAI_FAIL', -4), ('EAI_NONAME', -2), ('EAI_NODATA', -5), + # Windows defines EAI_NODATA as 11001 but idiotic getaddrinfo() + # implementation actually returns WSANO_DATA i.e. 11004. + ('WSANO_DATA', 11004), ] denied = ResourceDenied("Resource '%s' is not available" % resource_name) @@ -886,6 +961,33 @@ return captured_output("stdin") +_header = '2P' +if hasattr(sys, "gettotalrefcount"): + _header = '2P' + _header +_vheader = _header + 'P' + +def calcobjsize(fmt): + return struct.calcsize(_header + fmt + '0P') + +def calcvobjsize(fmt): + return struct.calcsize(_vheader + fmt + '0P') + + +_TPFLAGS_HAVE_GC = 1<<14 +_TPFLAGS_HEAPTYPE = 1<<9 + +def check_sizeof(test, o, size): + result = sys.getsizeof(o) + # add GC header size + if (_testcapi and\ + (type(o) == type) and (o.__flags__ & _TPFLAGS_HEAPTYPE) or\ + ((type(o) != type) and (type(o).__flags__ & _TPFLAGS_HAVE_GC))): + size += _testcapi.SIZEOF_PYGC_HEAD + msg = 'wrong size for %s: got %d, expected %d' \ + % (type(o), result, size) + test.assertEqual(result, size, msg) + + #======================================================================= # Decorator for running a function in a different locale, correctly resetting # it afterwards. @@ -994,7 +1096,7 @@ return wrapper return decorator -def precisionbigmemtest(size, memuse, overhead=5*_1M): +def precisionbigmemtest(size, memuse, overhead=5*_1M, dry_run=True): def decorator(f): def wrapper(self): if not real_max_memuse: @@ -1002,11 +1104,12 @@ else: maxsize = size - if real_max_memuse and real_max_memuse < maxsize * memuse: - if verbose: - sys.stderr.write("Skipping %s because of memory " - "constraint\n" % (f.__name__,)) - return + if ((real_max_memuse or not dry_run) + and real_max_memuse < maxsize * memuse): + if verbose: + sys.stderr.write("Skipping %s because of memory " + "constraint\n" % (f.__name__,)) + return return f(self, maxsize) wrapper.size = size @@ -1144,6 +1247,16 @@ suite.addTest(unittest.makeSuite(cls)) _run_suite(suite) +#======================================================================= +# Check for the presence of docstrings. + +HAVE_DOCSTRINGS = (check_impl_detail(cpython=False) or + sys.platform == 'win32' or + sysconfig.get_config_var('WITH_DOC_STRINGS')) + +requires_docstrings = unittest.skipUnless(HAVE_DOCSTRINGS, + "test requires docstrings") + #======================================================================= # doctest driver. @@ -1231,6 +1344,33 @@ except: break + at contextlib.contextmanager +def swap_attr(obj, attr, new_val): + """Temporary swap out an attribute with a new object. + + Usage: + with swap_attr(obj, "attr", 5): + ... + + This will set obj.attr to 5 for the duration of the with: block, + restoring the old value at the end of the block. If `attr` doesn't + exist on `obj`, it will be created and then deleted at the end of the + block. + """ + if hasattr(obj, attr): + real_val = getattr(obj, attr) + setattr(obj, attr, new_val) + try: + yield + finally: + setattr(obj, attr, real_val) + else: + setattr(obj, attr, new_val) + try: + yield + finally: + delattr(obj, attr) + def py3k_bytes(b): """Emulate the py3k bytes() constructor. diff --git a/Lib/test/test_threading_jy.py b/Lib/test/test_threading_jy.py --- a/Lib/test/test_threading_jy.py +++ b/Lib/test/test_threading_jy.py @@ -43,6 +43,15 @@ def _sleep(self, n): time.sleep(random.random()) + def test_issue1988(self): + cond = threading.Condition(threading.Lock()) + locked = False + try: + locked = cond.acquire(False) + finally: + if locked: + cond.release() + class TwistedTestCase(unittest.TestCase): diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -663,6 +663,11 @@ self.assertEqual(headers.get("Content-type"), mimetype) self.assertEqual(int(headers["Content-length"]), len(data)) + # This test isn't working on Ubuntu on an Apple Intel powerbook, + # Jython 2.7b1+ (default:6b4a1088566e, Feb 10 2013, 14:36:47) + # [OpenJDK 64-Bit Server VM (Oracle Corporation)] on java1.7.0_09 + @unittest.skipIf(test_support.is_jython, + "FIXME: Currently not working on jython") def test_file(self): import rfc822, socket h = urllib2.FileHandler() @@ -1112,7 +1117,6 @@ def test_basic_auth_with_single_quoted_realm(self): self.test_basic_auth(quote_char="'") - @unittest.skipIf(test_support.is_jython, "Currently not working on jython") def test_basic_auth_with_unquoted_realm(self): opener = OpenerDirector() password_manager = MockPasswordManager() diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -189,6 +189,7 @@ self.assertEqual(str(w[-1].message), text) self.assertTrue(w[-1].category is UserWarning) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CFilterTests(BaseTest, FilterTests): module = c_warnings @@ -351,6 +352,7 @@ self.module.warn(BadStrWarning()) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CWarnTests(BaseTest, WarnTests): module = c_warnings @@ -403,6 +405,7 @@ self.assertFalse(out.strip()) self.assertNotIn(b'RuntimeWarning', err) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CWCmdLineTests(BaseTest, WCmdLineTests): module = c_warnings @@ -410,6 +413,7 @@ module = py_warnings + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class _WarningsTests(BaseTest): """Tests specific to the _warnings module.""" @@ -593,6 +597,7 @@ file_object, expected_file_line) self.assertEqual(expect, file_object.getvalue()) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CWarningsDisplayTests(BaseTest, WarningsDisplayTests): module = c_warnings @@ -703,6 +708,7 @@ wmod.warn("foo") + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CCatchWarningTests(CatchWarningTests): module = c_warnings @@ -742,6 +748,7 @@ "['ignore::UnicodeWarning', 'ignore::DeprecationWarning']") self.assertEqual(p.wait(), 0) + at unittest.skipIf(test_support.is_jython, "No _warnings impl yet") class CEnvironmentVariableTests(EnvironmentVariableTests): module = c_warnings @@ -751,7 +758,9 @@ def test_main(): py_warnings.onceregistry.clear() - c_warnings.onceregistry.clear() + # No _warnings in _jython yet. + if not test_support.is_jython: + c_warnings.onceregistry.clear() test_support.run_unittest( CFilterTests, PyFilterTests, diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -24,15 +24,21 @@ self.assertEqual(zlib.crc32("", 1), 1) self.assertEqual(zlib.crc32("", 432), 432) - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") + def test_adler32(self): + self.assertEqual(zlib.adler32(""), zlib.adler32("", 1)) + + @unittest.skipIf(is_jython, "jython uses java.util.zip.Adler32, \ + which does not support a start value other than 1") def test_adler32start(self): - self.assertEqual(zlib.adler32(""), zlib.adler32("", 1)) self.assertTrue(zlib.adler32("abc", 0xffffffff)) - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") def test_adler32empty(self): + self.assertEqual(zlib.adler32("", 1), 1) + + @unittest.skipIf(is_jython, "jython uses java.util.zip.Adler32, \ + which does not support a start value other than 1") + def test_adler32empty_start(self): self.assertEqual(zlib.adler32("", 0), 0) - self.assertEqual(zlib.adler32("", 1), 1) self.assertEqual(zlib.adler32("", 432), 432) def assertEqual32(self, seen, expected): @@ -40,16 +46,19 @@ # This is important if bit 31 (0x08000000L) is set. self.assertEqual(seen & 0x0FFFFFFFFL, expected & 0x0FFFFFFFFL) - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") def test_penguins(self): self.assertEqual32(zlib.crc32("penguin", 0), 0x0e5c1a120L) self.assertEqual32(zlib.crc32("penguin", 1), 0x43b6aa94) - self.assertEqual32(zlib.adler32("penguin", 0), 0x0bcf02f6) self.assertEqual32(zlib.adler32("penguin", 1), 0x0bd602f7) self.assertEqual(zlib.crc32("penguin"), zlib.crc32("penguin", 0)) self.assertEqual(zlib.adler32("penguin"),zlib.adler32("penguin",1)) + @unittest.skipIf(is_jython, "jython uses java.util.zip.Adler32, \ + which does not support a start value other than 1") + def test_penguins_start(self): + self.assertEqual32(zlib.adler32("penguin", 0), 0x0bcf02f6) + def test_abcdefghijklmnop(self): """test issue1202 compliance: signed crc32, adler32 in 2.x""" foo = 'abcdefghijklmnop' @@ -101,23 +110,41 @@ class BaseCompressTestCase(object): - @unittest.skipIf(is_jython, "FIXME #1859: appears to hang on Jython") + def check_big_compress_buffer(self, size, compress_func): _1M = 1024 * 1024 - fmt = "%%0%dx" % (2 * _1M) - # Generate 10MB worth of random, and expand it by repeating it. - # The assumption is that zlib's memory is not big enough to exploit - # such spread out redundancy. - data = ''.join([binascii.a2b_hex(fmt % random.getrandbits(8 * _1M)) - for i in range(10)]) - data = data * (size // len(data) + 1) + if not is_jython: + # Generate 10MB worth of random, and expand it by repeating it. + # The assumption is that zlib's memory is not big enough to exploit + # such spread out redundancy. + fmt = "%%0%dx" % (2 * _1M) + data = ''.join([binascii.a2b_hex(fmt % random.getrandbits(8 * _1M)) + for i in range(10)]) + data = data * (size // len(data) + 1) + else: + # + # The original version of this test passes fine on cpython, + # but appears to hang on jython, because of the time taken to + # format a very large integer as a hexadecimal string. + # See this issue for details + # http://bugs.jython.org/issue2013 + # Since testing string formatting is not the purpose of the test + # it is necessary to generate the random test data in a different + # way on jython. (There may be a better way than what I have + # implemented here) + # + from java.math import BigInteger + from java.util import Random + num_bits = 8 * _1M # causes "java.lang.OutOfMemoryError: Java heap space" + num_bits = _1M + data = ''.join([str(BigInteger((num_bits), Random()).toByteArray()) + for i in range(10)]) try: compress_func(data) finally: # Release memory data = None - @unittest.skipIf(is_jython, "FIXME #1859: appears to hang on Jython") def check_big_decompress_buffer(self, size, decompress_func): data = 'x' * size try: @@ -146,7 +173,8 @@ x = zlib.compress(data) self.assertEqual(zlib.decompress(x), data) - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") + @unittest.skipIf(is_jython, "jython uses java.util.zip.Inflater, \ + which accepts incomplete streams without error") def test_incomplete_stream(self): # An useful error message is given x = zlib.compress(HAMLET_SCENE) @@ -163,6 +191,16 @@ @precisionbigmemtest(size=_1G + 1024 * 1024, memuse=2) def test_big_decompress_buffer(self, size): + """ + This is NOT testing for a 'size=_1G + 1024 * 1024', because of the definition of + the precisionbigmemtest decorator, which resets the value to 5147, based on + the definition of test_support.real_max_memuse == 0 + This is the case on my windows installation of python 2.7.3. + Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32 + And on my build of jython 2.7 + Jython 2.7b1+ (default:d5a22e9b622a, Feb 9 2013, 20:36:27) + [Java HotSpot(TM) Client VM (Sun Microsystems Inc.)] on java1.6.0_29 + """ self.check_big_decompress_buffer(size, zlib.decompress) @@ -389,13 +427,22 @@ dco = zlib.decompressobj() self.assertEqual(dco.flush(), "") # Returns nothing - @unittest.skipIf(is_jython, "FIXME #1859: not working on Jython") def test_decompress_incomplete_stream(self): # This is 'foo', deflated x = 'x\x9cK\xcb\xcf\x07\x00\x02\x82\x01E' # For the record self.assertEqual(zlib.decompress(x), 'foo') - self.assertRaises(zlib.error, zlib.decompress, x[:-5]) + if not is_jython: + # There is inconsistency between cpython zlib.decompress (which does not accept + # incomplete streams) and zlib.decompressobj().decompress (which does accept + # incomplete streams, the whole point of this test) + # On jython, both zlib.decompress and zlib.decompressobject().decompress behave + # the same way: they both accept incomplete streams. + # Therefore, imposing this precondition is cpython specific + # and not appropriate on jython, which has consistent behaviour. + # http://bugs.python.org/issue8672 + # http://bugs.jython.org/issue1859 + self.assertRaises(zlib.error, zlib.decompress, x[:-5]) # Omitting the stream end works with decompressor objects # (see issue #8672). dco = zlib.decompressobj() @@ -473,6 +520,16 @@ @precisionbigmemtest(size=_1G + 1024 * 1024, memuse=2) def test_big_decompress_buffer(self, size): + """ + This is NOT testing for a 'size=_1G + 1024 * 1024', because of the definition of + the precisionbigmemtest decorator, which resets the value to 5147, based on + the definition of test_support.real_max_memuse == 0 + This is the case on my windows installation of python 2.7.3. + Python 2.7.3 (default, Apr 10 2012, 23:31:26) [MSC v.1500 32 bit (Intel)] on win32 + And on my build of jython 2.7 + Jython 2.7b1+ (default:d5a22e9b622a, Feb 9 2013, 20:36:27) + [Java HotSpot(TM) Client VM (Sun Microsystems Inc.)] on java1.6.0_29 + """ d = zlib.decompressobj() decompress = lambda s: d.decompress(s) + d.flush() self.check_big_decompress_buffer(size, decompress) diff --git a/Lib/zlib.py b/Lib/zlib.py --- a/Lib/zlib.py +++ b/Lib/zlib.py @@ -61,16 +61,21 @@ if level < Z_BEST_SPEED or level > Z_BEST_COMPRESSION: raise error, "Bad compression level" deflater = Deflater(level, 0) - string = _to_input(string) - deflater.setInput(string, 0, len(string)) - deflater.finish() - return _get_deflate_data(deflater) + try: + string = _to_input(string) + deflater.setInput(string, 0, len(string)) + deflater.finish() + return _get_deflate_data(deflater) + finally: + deflater.end() def decompress(string, wbits=0, bufsize=16384): inflater = Inflater(wbits < 0) - inflater.setInput(_to_input(string)) - - return _get_inflate_data(inflater) + try: + inflater.setInput(_to_input(string)) + return _get_inflate_data(inflater) + finally: + inflater.end() class compressobj: # all jython uses wbits for is deciding whether to skip the header if it's negative diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -1,7 +1,15 @@ Jython NEWS -Jython 2.7a3 +Jython 2.7b2 Bugs Fixed + - [ 1926 ] Adjust MutableSet.pop test so we do not need to skip it + - [ 2020 ] str.translate should delete characters in the second arg when table is None + - [ 1753 ] zlib doesn't call end() on compress and decompress + +Jython 2.7b1 + Bugs Fixed + - [ 1716 ] xrange slicing raises NPE. + - [ 1968 ] Fixes for test_csv.py. - [ 1989 ] condition.notify_all() is missing. - [ 1994 ] threading.Event doesn't have is_set method fixed. - [ 1327 ] ThreadState needs API cleanup work @@ -33,7 +41,26 @@ Bugs Fixed - [ 1880 ] Sha 224 library not present in Jython +Jython 2.5.4rc2 + Bugs Fixed + - [ 2017 ] jython.bat script pollutes environment! (variables) + - [ 1899 ] Fix to platform.py to correctly avoid a warning message on Windows. + - [ 1988 ] API for threading.condition fails to accept *args for acquire + - [ 1753 ] zlib doesn't call end() on compress and decompress + - [ 1971 ] platform.py - 'NoneType' object has no attribute 'groups' + - [ 1988 ] API for threading.condition fails to accept *args for acquire + +Jython 2.5.4rc1 + Bugs Fixed + - [ 1936 ] JBoss 7, vfs protocol in use for jarFileName in PySystemState. + - [ 1972 ] jython 2.5.3 sys.stdin.readline() hangs when jython launched as subprocess on Mac OS X. + - [ 1962 ] Interactive console in Jython 2.5.3 needs CRTL-D to execute command. + - [ 1676 ] NPE in defaultdict + - [ 1481 ] jython throws java.lang.IllegalArgumentException instead of ValueError. + - [ 1716 ] xrange slicing raises NPE. + Jython 2.5.3rc1 + Bugs Fixed - [ 1952 ] __import__(): Handling of non-dict globals argument incompatible with CPython - [ 1900 ] Python imports from Java cause some Python imports to fail diff --git a/README.txt b/README.txt --- a/README.txt +++ b/README.txt @@ -1,15 +1,23 @@ -Welcome to Jython 2.7.0a2 -========================= +Welcome to Jython 2.7b1 +======================= -This is the second alpha release of the 2.7.0 version of Jython. Thanks to +This is the first beta release of the 2.7 version of Jython. Thanks to Adconion Media Group (http://www.adconion.com/) for sponsoring this release. -Thanks to all who contributed to Jython. +Thanks to all who contribute to Jython. -This release fixes a bug that left site-packages out of the path. This caused -many problems, including a failure of ez_setup.py to work. +Jython 2.7b1 brings us up to language level compatibility with the 2.7 version +of CPython. We have focused largely on CPython compatibility, and so this +release of Jython can run more pure Python apps then any previous release. +Please see the NEWS file for detailed release notes. Some notable new features +in this release are: a bytearray implementation, a buffer api, memoryview, a +bz2 module. + +As the first beta release, this marks a change in the focus of development +from adding features to bug fixing and on getting more of the test suite +running properly. Please see the NEWS file for detailed release notes. -The release was compiled on Ubuntu with JDK 6 and requires JDK 6 to run. +The release was compiled on Ubuntu with JDK 7 and requires JDK 6 to run. Please try this out and report any bugs at http://bugs.jython.org. diff --git a/build.xml b/build.xml --- a/build.xml +++ b/build.xml @@ -9,16 +9,16 @@ This build will create directories /build and /dist below basedir. -Use case 2: full build for a release (using svn checkout) +Use case 2: full build for a release (using hg checkout) --------------------------------------------------------- - - make sure you have access to the Jython Subversion repository - (https://jython.svn.sourceforge.net/svnroot/jython/trunk) - - override svn.main.dir in ant.properties (if necessary) + - make sure you have access to the Jython mercurial repository + (http://hg.python.org/jython) + - override ant.properties (if necessary) - call target 'full-build' This build will create a working directory named -full_build/${svn.main.dir} at the same level as your local directories -jython, sandbox and installer. It will contain a big +full_build at the same level as your local directories +jython and installer. It will contain a big jython_installer-${jython.version}.jar file suitable for installation. To build older releases, it may be necessary to use an older @@ -58,45 +58,6 @@ #debug=false #deprecation=off -# - the svn main directory to build from; only needed for full-build -# This e.g. could be one of: -# trunk -# branches/Release_2_2maint -# tags/Release_2_2rc3 -# meaning any directory just above the two directories: -# /installer -# /jython -# svn.main.dir defaults to trunk -#svn.main.dir=trunk - -# - the revision; only needed for a snapshot full-build -# To create a snapshot build: uncomment the two revision lines, and indicate the correct revision (it has to be a number) -# For 'normal' builds, this defaults to the latest revision on svn.main.dir (HEAD) -#svn.revision=7114 -#snapshot.revision=${svn.revision} - -# - the directory containing libsvnjavahl-1.dll (on windows) and svnjavahl.jar; only needed for full-build -# how to get these (for windows): -# - goto http://subversion.tigris.org/servlets/ProjectDocumentList -# - open the Releases folder -# - click on the Windows folder -# - download svn-win32-1.4.6_javahl.zip (or newer) -# - unzip the .dll and .jar into javahl.dir -javahl.dir=C:/Programme/Subversion/javahl - -# - the directory containing the svnant related .jar files; only needed for full-build -# the following .jar files (probably) are needed: -# - commons-lang-2.0.jar -# - jakarta-regexp-1.3.jar -# - svnant.jar -# - svnClientAdapter.jar -# - svnjavahl.jar -# how to get these: -# - goto http://subclipse.tigris.org/servlets/ProjectDocumentList -# - click on the the svnant folder -# - download svnant-1.0.0.zip (or newer) -# - unzip the jar files from the /lib folder to svnant.jar.dir -svnant.jar.dir=${basedir}/../externals/svnant-jars @@ -104,7 +65,7 @@ - + @@ -123,23 +84,28 @@ - - + + - + - + + + + + @@ -248,18 +214,14 @@ - - - - @@ -272,15 +234,6 @@ - - - - - - - - - @@ -316,11 +269,8 @@ debug = '${debug}' nowarn = '${nowarn}' --- properties (used for full-build only) --- - svn.main.dir = '${svn.main.dir}' - svn.revision = '${svn.revision}' checkout.dir = '${checkout.dir}' javahl.dir = '${javahl.dir}' - svnant.jar.dir = '${svnant.jar.dir}' do.snapshot.build = '${do.snapshot.build}' snapshot.revision = '${snapshot.revision}' do.checkout = '${do.checkout}' @@ -407,15 +357,6 @@ --> - - - - - - - - - @@ -423,8 +364,10 @@ - - + + + + @@ -475,7 +418,7 @@ + value='2.7a${xxx.revision}' /> ======================= -------------------------- @@ -683,7 +626,6 @@
- @@ -706,7 +648,7 @@
- + @@ -742,7 +684,7 @@
- + @@ -773,7 +715,7 @@ - + @@ -894,17 +836,17 @@ - - + - - - compiling installer from ${install.src.dir} - + + compiling installer from ${installer.src.dir} + - + copy installer icon to ${dist.dir} - + @@ -931,14 +873,13 @@ building installer .jar file - + - @@ -950,14 +891,14 @@ - +
- + diff --git a/extlibs/svnant-jars/svnClientAdapter.jar b/extlibs/svnant-jars/svnClientAdapter.jar deleted file mode 100644 Binary file extlibs/svnant-jars/svnClientAdapter.jar has changed diff --git a/extlibs/svnant-jars/svnant.jar b/extlibs/svnant-jars/svnant.jar deleted file mode 100644 Binary file extlibs/svnant-jars/svnant.jar has changed diff --git a/extlibs/svnant-jars/svnjavahl.jar b/extlibs/svnant-jars/svnjavahl.jar deleted file mode 100644 Binary file extlibs/svnant-jars/svnjavahl.jar has changed diff --git a/installer/src/java/org/apache/LICENSE.txt b/installer/src/java/org/apache/LICENSE.txt new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/LICENSE.txt @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/installer/src/java/org/apache/commons/cli/AlreadySelectedException.java b/installer/src/java/org/apache/commons/cli/AlreadySelectedException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/AlreadySelectedException.java @@ -0,0 +1,81 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +/** + *

Thrown when more than one option in an option group + * has been provided.

+ * + * @author John Keyes ( john at integralsource.com ) + * @see ParseException + */ +public class AlreadySelectedException extends ParseException { + + /** + *

Construct a new AlreadySelectedException + * with the specified detail message.

+ * + * @param message the detail message + */ + public AlreadySelectedException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/BasicParser.java b/installer/src/java/org/apache/commons/cli/BasicParser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/BasicParser.java @@ -0,0 +1,92 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +/** + * The class BasicParser provides a very simple implementation of + * the {@link Parser#flatten(Options,String[],boolean) flatten} method. + * + * @author John Keyes (john at integralsource.com) + * @see Parser + */ +public class BasicParser extends Parser { + + /** + *

A simple implementation of {@link Parser}'s abstract + * {@link Parser#flatten(Options,String[],boolean) flatten} method.

+ * + *

Note: options and stopAtNonOption + * are not used in this flatten method.

+ * + * @param options The command line {@link Options} + * @param arguments The command line arguments to be parsed + * @param stopAtNonOption Specifies whether to stop flattening + * when an non option is found. + * @return The arguments String array. + */ + protected String[] flatten( Options options, + String[] arguments, + boolean stopAtNonOption ) + { + // just echo the arguments + return arguments; + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/CommandLine.java b/installer/src/java/org/apache/commons/cli/CommandLine.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/CommandLine.java @@ -0,0 +1,328 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.LinkedList; +import java.util.Map; + +/** + *

Represents list of arguments parsed against + * a {@link Options} descriptor.

+ * + *

It allows querying of a boolean {@link #hasOption(String opt)}, + * in addition to retrieving the {@link #getOptionValue(String opt)} + * for options requiring arguments.

+ * + *

Additionally, any left-over or unrecognized arguments, + * are available for further processing.

+ * + * @author bob mcwhirter (bob @ werken.com) + * @author James Strachan + * @author John Keyes (john at integralsource.com) + */ +public class CommandLine { + + /** the unrecognised options/arguments */ + private List args = new LinkedList(); + + /** the processed options */ + private Map options = new HashMap(); + + /** Map of unique options for ease to get complete list of options */ + private Map hashcodeMap = new HashMap(); + + /** the processed options */ + private Option[] optionsArray; + + /** + *

Creates a command line.

+ */ + CommandLine() { + } + + /** + *

Query to see if an option has been set.

+ * + * @param opt Short name of the option + * @return true if set, false if not + */ + public boolean hasOption(String opt) { + return options.containsKey( opt ); + } + + /** + *

Query to see if an option has been set.

+ * + * @param opt character name of the option + * @return true if set, false if not + */ + public boolean hasOption( char opt ) { + return hasOption( String.valueOf( opt ) ); + } + + /** + *

Return the Object type of this Option.

+ * + * @param opt the name of the option + * @return the type of this Option + */ + public Object getOptionObject( String opt ) { + String res = getOptionValue( opt ); + + Object type = ((Option)((List)options.get(opt)).iterator().next()).getType(); + return res == null ? null : TypeHandler.createValue(res, type); + } + + /** + *

Return the Object type of this Option.

+ * + * @param opt the name of the option + * @return the type of opt + */ + public Object getOptionObject( char opt ) { + return getOptionObject( String.valueOf( opt ) ); + } + + /** + *

Retrieve the argument, if any, of this option.

+ * + * @param opt the name of the option + * @return Value of the argument if option is set, and has an argument, + * otherwise null. + */ + public String getOptionValue( String opt ) { + String[] values = getOptionValues(opt); + return (values == null) ? null : values[0]; + } + + /** + *

Retrieve the argument, if any, of this option.

+ * + * @param opt the character name of the option + * @return Value of the argument if option is set, and has an argument, + * otherwise null. + */ + public String getOptionValue( char opt ) { + return getOptionValue( String.valueOf( opt ) ); + } + + /** + *

Retrieves the array of values, if any, of an option.

+ * + * @param opt string name of the option + * @return Values of the argument if option is set, and has an argument, + * otherwise null. + */ + public String[] getOptionValues( String opt ) { + List values = new java.util.ArrayList(); + + if( options.containsKey( opt ) ) { + List opts = (List)options.get( opt ); + Iterator iter = opts.iterator(); + + while( iter.hasNext() ) { + Option optt = (Option)iter.next(); + values.addAll( optt.getValuesList() ); + } + } + return (values.size() == 0) ? null : (String[])values.toArray(new String[]{}); + } + + /** + *

Retrieves the array of values, if any, of an option.

+ * + * @param opt character name of the option + * @return Values of the argument if option is set, and has an argument, + * otherwise null. + */ + public String[] getOptionValues( char opt ) { + return getOptionValues( String.valueOf( opt ) ); + } + + /** + *

Retrieve the argument, if any, of an option.

+ * + * @param opt name of the option + * @param defaultValue is the default value to be returned if the option is not specified + * @return Value of the argument if option is set, and has an argument, + * otherwise defaultValue. + */ + public String getOptionValue( String opt, String defaultValue ) { + String answer = getOptionValue( opt ); + return ( answer != null ) ? answer : defaultValue; + } + + /** + *

Retrieve the argument, if any, of an option.

+ * + * @param opt character name of the option + * @param defaultValue is the default value to be returned if the option is not specified + * @return Value of the argument if option is set, and has an argument, + * otherwise defaultValue. + */ + public String getOptionValue( char opt, String defaultValue ) { + return getOptionValue( String.valueOf( opt ), defaultValue ); + } + + /** + *

Retrieve any left-over non-recognized options and arguments

+ * + * @return remaining items passed in but not parsed as an array + */ + public String[] getArgs() { + String[] answer = new String[ args.size() ]; + args.toArray( answer ); + return answer; + } + + /** + *

Retrieve any left-over non-recognized options and arguments

+ * + * @return remaining items passed in but not parsed as a List. + */ + public List getArgList() { + return args; + } + + /** + * jkeyes + * - commented out until it is implemented properly + *

Dump state, suitable for debugging.

+ * + * @return Stringified form of this object + */ + /* + public String toString() { + StringBuffer buf = new StringBuffer(); + + buf.append( "[ CommandLine: [ options: " ); + buf.append( options.toString() ); + buf.append( " ] [ args: "); + buf.append( args.toString() ); + buf.append( " ] ]" ); + + return buf.toString(); + } + */ + + /** + *

Add left-over unrecognized option/argument.

+ * + * @param arg the unrecognised option/argument. + */ + void addArg(String arg) { + args.add( arg ); + } + + /** + *

Add an option to the command line. The values of + * the option are stored.

+ * + * @param opt the processed option + */ + void addOption( Option opt ) { + hashcodeMap.put( new Integer( opt.hashCode() ), opt ); + + String key = opt.getOpt(); + if( " ".equals(key) ) { + key = opt.getLongOpt(); + } + + if( options.get( key ) != null ) { + ((java.util.List)options.get( key )).add( opt ); + } + else { + options.put( key, new java.util.ArrayList() ); + ((java.util.List)options.get( key ) ).add( opt ); + } + } + + /** + *

Returns an iterator over the Option members of CommandLine.

+ * + * @return an Iterator over the processed {@link Option} + * members of this {@link CommandLine} + */ + public Iterator iterator( ) { + return hashcodeMap.values().iterator(); + } + + /** + *

Returns an array of the processed {@link Option}s.

+ * + * @return an array of the processed {@link Option}s. + */ + public Option[] getOptions( ) { + Collection processed = hashcodeMap.values(); + + // reinitialise array + optionsArray = new Option[ processed.size() ]; + + // return the array + return (Option[]) processed.toArray( optionsArray ); + } + +} diff --git a/installer/src/java/org/apache/commons/cli/CommandLineParser.java b/installer/src/java/org/apache/commons/cli/CommandLineParser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/CommandLineParser.java @@ -0,0 +1,97 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +/** + * A class that implements the CommandLineParser interface + * can parse a String array according to the {@link Options} specified + * and return a {@link CommandLine}. + * + * @author John Keyes (john at integralsource.com) + */ +public interface CommandLineParser { + + /** + * Parse the arguments according to the specified options. + * + * @param options the specified Options + * @param arguments the command line arguments + * @return the list of atomic option and value tokens + * @throws ParseException if there are any problems encountered + * while parsing the command line tokens. + */ + public CommandLine parse( Options options, String[] arguments ) + throws ParseException; + + /** + * Parse the arguments according to the specified options. + * + * @param options the specified Options + * @param arguments the command line arguments + * @param stopAtNonOption specifies whether to continue parsing the + * arguments if a non option is encountered. + * @return the list of atomic option and value tokens + * @throws ParseException if there are any problems encountered + * while parsing the command line tokens. + */ + public CommandLine parse( Options options, String[] arguments, boolean stopAtNonOption ) + throws ParseException; +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/GnuParser.java b/installer/src/java/org/apache/commons/cli/GnuParser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/GnuParser.java @@ -0,0 +1,187 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.commons.cli; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +/** + * The class GnuParser provides an implementation of the + * {@link Parser#flatten(Options,String[],boolean) flatten} method. + * + * @author John Keyes (john at integralsource.com) + * @see Parser + * @version $Revision: 2662 $ + */ +public class GnuParser extends Parser { + + /** holder for flattened tokens */ + private ArrayList tokens = new ArrayList(); + + /** + *

Resets the members to their original state i.e. remove + * all of tokens entries. + */ + private void init() { + tokens.clear(); + } + + /** + *

This flatten method does so using the following rules: + *

    + *
  1. If an {@link Option} exists for the first character of + * the arguments entry AND an {@link Option} + * does not exist for the whole argument then + * add the first character as an option to the processed tokens + * list e.g. "-D" and add the rest of the entry to the also.
  2. + *
  3. Otherwise just add the token to the processed tokens list. + *
  4. + *
+ *

+ */ + protected String[] flatten( Options options, + String[] arguments, + boolean stopAtNonOption ) + { + init(); + boolean eatTheRest = false; + Option currentOption = null; + + for( int i = 0; i < arguments.length; i++ ) { + if( "--".equals( arguments[i] ) ) { + eatTheRest = true; + tokens.add( "--" ); + } + else if ( "-".equals( arguments[i] ) ) { + tokens.add( "-" ); + } + else if( arguments[i].startsWith( "-" ) ) { + Option option = options.getOption( arguments[i] ); + + // this is not an Option + if( option == null ) { + // handle special properties Option + Option specialOption = options.getOption( arguments[i].substring(0,2) ); + if( specialOption != null ) { + tokens.add( arguments[i].substring(0,2) ); + tokens.add( arguments[i].substring(2) ); + } + else if( stopAtNonOption ) { + eatTheRest = true; + tokens.add( arguments[i] ); + } + else { + tokens.add( arguments[i] ); + } + } + else { + currentOption = option; + // special option + Option specialOption = options.getOption( arguments[i].substring(0,2) ); + if( specialOption != null && option == null ) { + tokens.add( arguments[i].substring(0,2) ); + tokens.add( arguments[i].substring(2) ); + } + else if( currentOption != null && currentOption.hasArg() ) { + if( currentOption.hasArg() ) { + tokens.add( arguments[i] ); + currentOption= null; + } + else if ( currentOption.hasArgs() ) { + tokens.add( arguments[i] ); + } + else if ( stopAtNonOption ) { + eatTheRest = true; + tokens.add( "--" ); + tokens.add( arguments[i] ); + } + else { + tokens.add( arguments[i] ); + } + } + else if (currentOption != null ) { + tokens.add( arguments[i] ); + } + else if ( stopAtNonOption ) { + eatTheRest = true; + tokens.add( "--" ); + tokens.add( arguments[i] ); + } + else { + tokens.add( arguments[i] ); + } + } + } + else { + tokens.add( arguments[i] ); + } + + if( eatTheRest ) { + for( i++; i < arguments.length; i++ ) { + tokens.add( arguments[i] ); + } + } + } + return (String[])tokens.toArray( new String[] {} ); + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/HelpFormatter.java b/installer/src/java/org/apache/commons/cli/HelpFormatter.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/HelpFormatter.java @@ -0,0 +1,542 @@ +/* + * $Header$ + * $Revision: 2690 $ + * $Date: 2006-03-25 23:39:57 -0800 (Sat, 25 Mar 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * A formatter of help messages for the current command line options + * + * @author Slawek Zachcial + * @author John Keyes (john at integralsource.com) + **/ +public class HelpFormatter +{ + // --------------------------------------------------------------- Constants + + public static final int DEFAULT_WIDTH = 74; + public static final int DEFAULT_LEFT_PAD = 1; + public static final int DEFAULT_DESC_PAD = 3; + public static final String DEFAULT_SYNTAX_PREFIX = "usage: "; + public static final String DEFAULT_OPT_PREFIX = "-"; + public static final String DEFAULT_LONG_OPT_PREFIX = "--"; + public static final String DEFAULT_ARG_NAME = "arg"; + + // ------------------------------------------------------------------ Static + + // -------------------------------------------------------------- Attributes + + public int defaultWidth; + public int defaultLeftPad; + public int defaultDescPad; + public String defaultSyntaxPrefix; + public String defaultNewLine; + public String defaultOptPrefix; + public String defaultLongOptPrefix; + public String defaultArgName; + + // ------------------------------------------------------------ Constructors + public HelpFormatter() + { + defaultWidth = DEFAULT_WIDTH; + defaultLeftPad = DEFAULT_LEFT_PAD; + defaultDescPad = DEFAULT_DESC_PAD; + defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX; + defaultNewLine = System.getProperty("line.separator"); + defaultOptPrefix = DEFAULT_OPT_PREFIX; + defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX; + defaultArgName = DEFAULT_ARG_NAME; + } + + // ------------------------------------------------------------------ Public + + public void printHelp( String cmdLineSyntax, + Options options ) + { + printHelp( defaultWidth, cmdLineSyntax, null, options, null, false ); + } + + public void printHelp( String cmdLineSyntax, + Options options, + boolean autoUsage ) + { + printHelp( defaultWidth, cmdLineSyntax, null, options, null, autoUsage ); + } + + public void printHelp( String cmdLineSyntax, + String header, + Options options, + String footer ) + { + printHelp( cmdLineSyntax, header, options, footer, false ); + } + + public void printHelp( String cmdLineSyntax, + String header, + Options options, + String footer, + boolean autoUsage ) + { + printHelp(defaultWidth, cmdLineSyntax, header, options, footer, autoUsage ); + } + + public void printHelp( int width, + String cmdLineSyntax, + String header, + Options options, + String footer ) + { + printHelp( width, cmdLineSyntax, header, options, footer, false ); + } + + public void printHelp( int width, + String cmdLineSyntax, + String header, + Options options, + String footer, + boolean autoUsage ) + { + PrintWriter pw = new PrintWriter(System.out); + printHelp( pw, width, cmdLineSyntax, header, + options, defaultLeftPad, defaultDescPad, footer, autoUsage ); + pw.flush(); + } + public void printHelp( PrintWriter pw, + int width, + String cmdLineSyntax, + String header, + Options options, + int leftPad, + int descPad, + String footer ) + throws IllegalArgumentException + { + printHelp( pw, width, cmdLineSyntax, header, options, leftPad, descPad, footer, false ); + } + + public void printHelp( PrintWriter pw, + int width, + String cmdLineSyntax, + String header, + Options options, + int leftPad, + int descPad, + String footer, + boolean autoUsage ) + throws IllegalArgumentException + { + if ( cmdLineSyntax == null || cmdLineSyntax.length() == 0 ) + { + throw new IllegalArgumentException("cmdLineSyntax not provided"); + } + + if ( autoUsage ) { + printUsage( pw, width, cmdLineSyntax, options ); + } + else { + printUsage( pw, width, cmdLineSyntax ); + } + + if ( header != null && header.trim().length() > 0 ) + { + printWrapped( pw, width, header ); + } + printOptions( pw, width, options, leftPad, descPad ); + if ( footer != null && footer.trim().length() > 0 ) + { + printWrapped( pw, width, footer ); + } + } + + /** + *

Prints the usage statement for the specified application.

+ * + * @param pw The PrintWriter to print the usage statement + * @param width ?? + * @param appName The application name + * @param options The command line Options + * + */ + public void printUsage( PrintWriter pw, int width, String app, Options options ) + { + // initialise the string buffer + StringBuffer buff = new StringBuffer( defaultSyntaxPrefix ).append( app ).append( " " ); + + // create a list for processed option groups + ArrayList list = new ArrayList(); + + // temp variable + Option option; + + // iterate over the options + for ( Iterator i = options.getOptions().iterator(); i.hasNext(); ) + { + // get the next Option + option = (Option) i.next(); + + // check if the option is part of an OptionGroup + OptionGroup group = options.getOptionGroup( option ); + + // if the option is part of a group + if( group != null ) { + // if the group has not already been processed + if(!list.contains(group)) { + + // add the group to the processed list + list.add( group ); + + // get the names of the options from the OptionGroup + Collection names = group.getNames(); + + buff.append( "[" ); + + // for each option in the OptionGroup + for( Iterator iter = names.iterator(); iter.hasNext(); ) { + buff.append( iter.next() ); + if( iter.hasNext() ) { + buff.append( " | " ); + } + } + buff.append( "] " ); + } + } else { // if the Option is not part of an OptionGroup + // if the Option is not a required option + if( !option.isRequired() ) { + buff.append( "[" ); + } + + if( !" ".equals( option.getOpt() ) ) { + buff.append( "-" ).append( option.getOpt() ); + } + else { + buff.append( "--" ).append( option.getLongOpt() ); + } + + if( option.hasArg() ){ + buff.append( " " ); + } + + // if the Option has a value + if( option.hasArg() ) { + buff.append( option.getArgName() ); + } + + // if the Option is not a required option + if( !option.isRequired() ) { + buff.append( "]" ); + } + buff.append( " " ); + } + } + + // call printWrapped + printWrapped( pw, width, buff.toString().indexOf(' ')+1, + buff.toString() ); + } + + public void printUsage( PrintWriter pw, int width, String cmdLineSyntax ) + { + int argPos = cmdLineSyntax.indexOf(' ') + 1; + printWrapped(pw, width, defaultSyntaxPrefix.length() + argPos, + defaultSyntaxPrefix + cmdLineSyntax); + } + + public void printOptions( PrintWriter pw, int width, Options options, int leftPad, int descPad ) + { + StringBuffer sb = new StringBuffer(); + renderOptions(sb, width, options, leftPad, descPad); + pw.println(sb.toString()); + } + + public void printWrapped( PrintWriter pw, int width, String text ) + { + printWrapped(pw, width, 0, text); + } + + public void printWrapped( PrintWriter pw, int width, int nextLineTabStop, String text ) + { + StringBuffer sb = new StringBuffer(text.length()); + renderWrappedText(sb, width, nextLineTabStop, text); + pw.println(sb.toString()); + } + + // --------------------------------------------------------------- Protected + + protected StringBuffer renderOptions( StringBuffer sb, + int width, + Options options, + int leftPad, + int descPad ) + { + final String lpad = createPadding(leftPad); + final String dpad = createPadding(descPad); + + //first create list containing only -a,--aaa where -a is opt and --aaa is + //long opt; in parallel look for the longest opt string + //this list will be then used to sort options ascending + int max = 0; + StringBuffer optBuf; + List prefixList = new ArrayList(); + Option option; + List optList = options.helpOptions(); + if (!options.isSortAsAdded()) { + Collections.sort(optList, new StringBufferComparator()); + } + for ( Iterator i = optList.iterator(); i.hasNext(); ) + { + option = (Option) i.next(); + optBuf = new StringBuffer(8); + + if (option.getOpt().equals(" ")) + { + optBuf.append(lpad).append(" " + defaultLongOptPrefix).append(option.getLongOpt()); + } + else + { + optBuf.append(lpad).append(defaultOptPrefix).append(option.getOpt()); + if ( option.hasLongOpt() ) + { + optBuf.append(',').append(defaultLongOptPrefix).append(option.getLongOpt()); + } + + } + + if( option.hasArg() ) { + if( option.hasArgName() ) { + optBuf.append(" <").append( option.getArgName() ).append( '>' ); + } + else { + optBuf.append(' '); + } + } + + prefixList.add(optBuf); + max = optBuf.length() > max ? optBuf.length() : max; + } + int x = 0; + for ( Iterator i = optList.iterator(); i.hasNext(); ) + { + option = (Option) i.next(); + optBuf = new StringBuffer( prefixList.get( x++ ).toString() ); + + if ( optBuf.length() < max ) + { + optBuf.append(createPadding(max - optBuf.length())); + } + optBuf.append( dpad ); + + int nextLineTabStop = max + descPad; + renderWrappedText(sb, width, nextLineTabStop, + optBuf.append(option.getDescription()).toString()); + if ( i.hasNext() ) + { + sb.append(defaultNewLine); + } + } + + return sb; + } + + protected StringBuffer renderWrappedText( StringBuffer sb, + int width, + int nextLineTabStop, + String text ) + { + int pos = findWrapPos( text, width, 0); + if ( pos == -1 ) + { + sb.append(rtrim(text)); + return sb; + } + else + { + sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); + } + + //all following lines must be padded with nextLineTabStop space characters + final String padding = createPadding(nextLineTabStop); + + while ( true ) + { + text = padding + text.substring(pos).trim(); + pos = findWrapPos( text, width, nextLineTabStop ); + if ( pos == -1 ) + { + sb.append(text); + return sb; + } + + sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine); + } + + } + + /** + * Finds the next text wrap position after startPos for the text + * in sb with the column width width. + * The wrap point is the last postion before startPos+width having a whitespace + * character (space, \n, \r). + * + * @param sb text to be analyzed + * @param width width of the wrapped text + * @param startPos position from which to start the lookup whitespace character + * @return postion on which the text must be wrapped or -1 if the wrap position is at the end + * of the text + */ + protected int findWrapPos( String text, int width, int startPos ) + { + int pos = -1; + // the line ends before the max wrap pos or a new line char found + if ( ((pos = text.indexOf('\n', startPos)) != -1 && pos <= width) || + ((pos = text.indexOf('\t', startPos)) != -1 && pos <= width) ) + { + return pos; + } + else if ( (startPos + width) >= text.length() ) + { + return -1; + } + + //look for the last whitespace character before startPos+width + pos = startPos + width; + char c; + while ( pos >= startPos && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r' ) + { + --pos; + } + //if we found it - just return + if ( pos > startPos ) + { + return pos; + } + else + { + //must look for the first whitespace chearacter after startPos + width + pos = startPos + width; + while ( pos <= text.length() && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r' ) + { + ++pos; + } + return pos == text.length() ? -1 : pos; + } + } + + protected String createPadding(int len) + { + StringBuffer sb = new StringBuffer(len); + for ( int i = 0; i < len; ++i ) + { + sb.append(' '); + } + return sb.toString(); + } + + protected String rtrim( String s ) + { + if ( s == null || s.length() == 0 ) + { + return s; + } + + int pos = s.length(); + while ( pos >= 0 && Character.isWhitespace(s.charAt(pos-1)) ) + { + --pos; + } + return s.substring(0, pos); + } + + // ------------------------------------------------------- Package protected + + // ----------------------------------------------------------------- Private + + // ----------------------------------------------------------- Inner classes + + private static class StringBufferComparator + implements Comparator + { + public int compare( Object o1, Object o2 ) + { + String str1 = stripPrefix(o1.toString()); + String str2 = stripPrefix(o2.toString()); + return (str1.compareTo(str2)); + } + + private String stripPrefix(String strOption) + { + // Strip any leading '-' characters + int iStartIndex = strOption.lastIndexOf('-'); + if (iStartIndex == -1) + { + iStartIndex = 0; + } + return strOption.substring(iStartIndex); + + } + } +} diff --git a/installer/src/java/org/apache/commons/cli/MissingArgumentException.java b/installer/src/java/org/apache/commons/cli/MissingArgumentException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/MissingArgumentException.java @@ -0,0 +1,82 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Thrown when an option requiring an argument + * is not provided with an argument.

+ * + * @author John Keyes (john at integralsource.com) + * @see ParseException + */ +public class MissingArgumentException extends ParseException { + + /** + *

Construct a new MissingArgumentException + * with the specified detail message.

+ * + * @param message the detail message + */ + public MissingArgumentException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/MissingOptionException.java b/installer/src/java/org/apache/commons/cli/MissingOptionException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/MissingOptionException.java @@ -0,0 +1,81 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Thrown when a required option has not been provided.

+ * + * @author John Keyes ( john at integralsource.com ) + * @see ParseException + */ +public class MissingOptionException extends ParseException { + + /** + *

Construct a new MissingSelectedException + * with the specified detail message.

+ * + * @param message the detail message + */ + public MissingOptionException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/Option.java b/installer/src/java/org/apache/commons/cli/Option.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/Option.java @@ -0,0 +1,575 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: Option.java 2662 2006-02-18 14:20:33Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import java.util.ArrayList; + +/**

Describes a single command-line option. It maintains + * information regarding the short-name of the option, the long-name, + * if any exists, a flag indicating if an argument is required for + * this option, and a self-documenting description of the option.

+ * + *

An Option is not created independantly, but is create through + * an instance of {@link Options}.

+ * + * @see org.apache.commons.cli.Options + * @see org.apache.commons.cli.CommandLine + * + * @author bob mcwhirter (bob @ werken.com) + * @author James Strachan + * @version $Revision: 2662 $ + */ + +public class Option implements Cloneable { + + /** constant that specifies the number of argument values has not been specified */ + public final static int UNINITIALIZED = -1; + + /** constant that specifies the number of argument values is infinite */ + public final static int UNLIMITED_VALUES = -2; + + /** opt the single character representation of the option */ + private String opt; + + /** longOpt is the long representation of the option */ + private String longOpt; + + /** hasArg specifies whether this option has an associated argument */ + private boolean hasArg; + + /** argName specifies the name of the argument for this option */ + private String argName; + + /** description of the option */ + private String description; + + /** required specifies whether this option is required to be present */ + private boolean required; + + /** specifies whether the argument value of this Option is optional */ + private boolean optionalArg; + + /** + * numberOfArgs specifies the number of argument values this option + * can have + */ + private int numberOfArgs = UNINITIALIZED; + + /** the type of this Option */ + private Object type; + + /** the list of argument values **/ + private ArrayList values = new ArrayList(); + + /** option char (only valid for single character options) */ + private char id; + + /** the character that is the value separator */ + private char valuesep; + + /** + *

Validates whether opt is a permissable Option + * shortOpt. The rules that specify if the opt + * is valid are:

+ *
    + *
  • opt is not NULL
  • + *
  • a single character opt that is either + * ' '(special case), '?', '@' or a letter
  • + *
  • a multi character opt that only contains + * letters.
  • + *
+ * + * @param opt The option string to validate + * @throws IllegalArgumentException if the Option is not valid. + */ + private void validateOption( String opt ) + throws IllegalArgumentException + { + // check that opt is not NULL + if( opt == null ) { + throw new IllegalArgumentException( "opt is null" ); + } + // handle the single character opt + else if( opt.length() == 1 ) { + char ch = opt.charAt( 0 ); + if ( !isValidOpt( ch ) ) { + throw new IllegalArgumentException( "illegal option value '" + + ch + "'" ); + } + id = ch; + } + // handle the multi character opt + else { + char[] chars = opt.toCharArray(); + for( int i = 0; i < chars.length; i++ ) { + if( !isValidChar( chars[i] ) ) { + throw new IllegalArgumentException( "opt contains illegal character value '" + chars[i] + "'" ); + } + } + } + } + + /** + *

Returns whether the specified character is a valid Option.

+ * + * @param c the option to validate + * @return true if c is a letter, ' ', '?' or '@', otherwise false. + */ + private boolean isValidOpt( char c ) { + return ( isValidChar( c ) || c == ' ' || c == '?' || c == '@' ); + } + + /** + *

Returns whether the specified character is a valid character.

+ * + * @param c the character to validate + * @return true if c is a letter. + */ + private boolean isValidChar( char c ) { + return Character.isJavaIdentifierPart( c ); + } + + /** + *

Returns the id of this Option. This is only set when the + * Option shortOpt is a single character. This is used for switch + * statements.

+ * + * @return the id of this Option + */ + public int getId( ) { + return id; + } + + /** + * Creates an Option using the specified parameters. + * + * @param opt short representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option( String opt, String description ) + throws IllegalArgumentException + { + this( opt, null, false, description ); + } + + /** + * Creates an Option using the specified parameters. + * + * @param opt short representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option( String opt, boolean hasArg, String description ) + throws IllegalArgumentException + { + this( opt, null, hasArg, description ); + } + + /** + *

Creates an Option using the specified parameters.

+ * + * @param opt short representation of the option + * @param longOpt the long representation of the option + * @param hasArg specifies whether the Option takes an argument or not + * @param description describes the function of the option + */ + public Option( String opt, String longOpt, boolean hasArg, String description ) + throws IllegalArgumentException + { + // ensure that the option is valid + validateOption( opt ); + + this.opt = opt; + this.longOpt = longOpt; + + // if hasArg is set then the number of arguments is 1 + if( hasArg ) { + this.numberOfArgs = 1; + } + + this.hasArg = hasArg; + this.description = description; + } + + /**

Retrieve the name of this Option.

+ * + *

It is this String which can be used with + * {@link CommandLine#hasOption(String opt)} and + * {@link CommandLine#getOptionValue(String opt)} to check + * for existence and argument.

+ * + * @return The name of this option + */ + public String getOpt() { + return this.opt; + } + + /** + *

Retrieve the type of this Option.

+ * + * @return The type of this option + */ + public Object getType() { + return this.type; + } + + /** + *

Sets the type of this Option.

+ * + * @param type the type of this Option + */ + public void setType( Object type ) { + this.type = type; + } + + /** + *

Retrieve the long name of this Option.

+ * + * @return Long name of this option, or null, if there is no long name + */ + public String getLongOpt() { + return this.longOpt; + } + + /** + *

Sets the long name of this Option.

+ * + * @param longOpt the long name of this Option + */ + public void setLongOpt( String longOpt ) { + this.longOpt = longOpt; + } + + /** + *

Sets whether this Option can have an optional argument.

+ * + * @param optionalArg specifies whether the Option can have + * an optional argument. + */ + public void setOptionalArg( boolean optionalArg ) { + this.optionalArg = optionalArg; + } + + /** + * @return whether this Option can have an optional argument + */ + public boolean hasOptionalArg( ) { + return this.optionalArg; + } + + /**

Query to see if this Option has a long name

+ * + * @return boolean flag indicating existence of a long name + */ + public boolean hasLongOpt() { + return ( this.longOpt != null ); + } + + /**

Query to see if this Option requires an argument

+ * + * @return boolean flag indicating if an argument is required + */ + public boolean hasArg() { + return this.numberOfArgs > 0 || numberOfArgs == UNLIMITED_VALUES; + } + + /**

Retrieve the self-documenting description of this Option

+ * + * @return The string description of this option + */ + public String getDescription() { + return this.description; + } + + /** + *

Query to see if this Option requires an argument

+ * + * @return boolean flag indicating if an argument is required + */ + public boolean isRequired() { + return this.required; + } + + /** + *

Sets whether this Option is mandatory.

+ * + * @param required specifies whether this Option is mandatory + */ + public void setRequired( boolean required ) { + this.required = required; + } + + /** + *

Sets the display name for the argument value.

+ * + * @param argName the display name for the argument value. + */ + public void setArgName( String argName ) { + this.argName = argName; + } + + /** + *

Gets the display name for the argument value.

+ * + * @return the display name for the argument value. + */ + public String getArgName() { + return this.argName; + } + + /** + *

Returns whether the display name for the argument value + * has been set.

+ * + * @return if the display name for the argument value has been + * set. + */ + public boolean hasArgName() { + return (this.argName != null && this.argName.length() > 0 ); + } + + /** + *

Query to see if this Option can take many values

+ * + * @return boolean flag indicating if multiple values are allowed + */ + public boolean hasArgs() { + return ( this.numberOfArgs > 1 || this.numberOfArgs == UNLIMITED_VALUES ); + } + + /** + *

Sets the number of argument values this Option can take.

+ * + * @param num the number of argument values + */ + public void setArgs( int num ) { + this.numberOfArgs = num; + } + + /** + *

Sets the value separator. For example if the argument value + * was a Java property, the value separator would be '='.

+ * + * @param sep The value separator. + */ + public void setValueSeparator( char sep ) { + this.valuesep = sep; + } + + /** + *

Returns the value separator character.

+ * + * @return the value separator character. + */ + public char getValueSeparator() { + return this.valuesep; + } + + /** + *

Returns the number of argument values this Option can take.

+ * + * @return num the number of argument values + */ + public int getArgs( ) { + return this.numberOfArgs; + } + + /** + *

Dump state, suitable for debugging.

+ * + * @return Stringified form of this object + */ + public String toString() { + StringBuffer buf = new StringBuffer().append("[ option: "); + + buf.append( this.opt ); + + if ( this.longOpt != null ) { + buf.append(" ") + .append(this.longOpt); + } + + buf.append(" "); + + if ( hasArg ) { + buf.append( "+ARG" ); + } + + buf.append(" :: ") + .append( this.description ); + + if ( this.type != null ) { + buf.append(" :: ") + .append( this.type ); + } + + buf.append(" ]"); + return buf.toString(); + } + + /** + *

Adds the specified value to this Option.

+ * + * @param value is a/the value of this Option + */ + public boolean addValue( String value ) { + + switch( numberOfArgs ) { + case UNINITIALIZED: + return false; + case UNLIMITED_VALUES: + if( getValueSeparator() > 0 ) { + int index = 0; + while( (index = value.indexOf( getValueSeparator() ) ) != -1 ) { + this.values.add( value.substring( 0, index ) ); + value = value.substring( index+1 ); + } + } + this.values.add( value ); + return true; + default: + if( getValueSeparator() > 0 ) { + int index = 0; + while( (index = value.indexOf( getValueSeparator() ) ) != -1 ) { + if( values.size() > numberOfArgs-1 ) { + return false; + } + this.values.add( value.substring( 0, index ) ); + value = value.substring( index+1 ); + } + } + if( values.size() > numberOfArgs-1 ) { + return false; + } + this.values.add( value ); + return true; + } + } + + /** + * @return the value/first value of this Option or + * null if there are no values. + */ + public String getValue() { + return this.values.size()==0 ? null : (String)this.values.get( 0 ); + } + + /** + * @return the specified value of this Option or + * null if there are no values. + */ + public String getValue( int index ) + throws IndexOutOfBoundsException + { + return ( this.values.size()==0 ) ? null : (String)this.values.get( index ); + } + + /** + * @return the value/first value of this Option or the + * defaultValue if there are no values. + */ + public String getValue( String defaultValue ) { + String value = getValue( ); + return ( value != null ) ? value : defaultValue; + } + + /** + * @return the values of this Option as a String array + * or null if there are no values + */ + public String[] getValues() { + return this.values.size()==0 ? null : (String[])this.values.toArray(new String[]{}); + } + + /** + * @return the values of this Option as a List + * or null if there are no values + */ + public java.util.List getValuesList() { + return this.values; + } + + /** + * @return a copy of this Option + */ + public Object clone() { + Option option = new Option( getOpt(), getDescription() ); + option.setArgs( getArgs() ); + option.setOptionalArg( hasOptionalArg() ); + option.setRequired( isRequired() ); + option.setLongOpt( getLongOpt() ); + option.setType( getType() ); + option.setValueSeparator( getValueSeparator() ); + return option; + } +} diff --git a/installer/src/java/org/apache/commons/cli/OptionBuilder.java b/installer/src/java/org/apache/commons/cli/OptionBuilder.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/OptionBuilder.java @@ -0,0 +1,368 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

OptionBuilder allows the user to create Options using descriptive + * methods.

+ *

Details on the Builder pattern can be found at + * http://c2.com/cgi-bin/wiki?BuilderPattern.

+ * + * @author John Keyes ( john at integralsource.com ) + * @since 1.0 + */ +public class OptionBuilder { + + /** long option */ + private static String longopt; + /** option description */ + private static String description; + /** argument name */ + private static String argName; + /** is required? */ + private static boolean required; + /** the number of arguments */ + private static int numberOfArgs = Option.UNINITIALIZED; + /** option type */ + private static Object type; + /** option can have an optional argument value */ + private static boolean optionalArg; + /** value separator for argument value */ + private static char valuesep; + + /** option builder instance */ + private static OptionBuilder instance = new OptionBuilder(); + + // private constructor + private OptionBuilder() { + } + + /** + *

Resets the member variables to their default values.

+ */ + private static void reset() { + description = null; + argName = null; + longopt = null; + type = null; + required = false; + numberOfArgs = Option.UNINITIALIZED; + + // PMM 9/6/02 - these were missing + optionalArg = false; + valuesep = (char) 0; + } + + /** + *

The next Option created will have the following long option value.

+ * + * @param longopt the long option value + * @return the OptionBuilder instance + */ + public static OptionBuilder withLongOpt( String longopt ) { + instance.longopt = longopt; + return instance; + } + + /** + *

The next Option created will require an argument value.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArg( ) { + instance.numberOfArgs = 1; + return instance; + } + + /** + *

The next Option created will require an argument value if + * hasArg is true.

+ * + * @param hasArg if true then the Option has an argument value + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArg( boolean hasArg ) { + instance.numberOfArgs = ( hasArg == true ) ? 1 : Option.UNINITIALIZED; + return instance; + } + + /** + *

The next Option created will have the specified argument value + * name.

+ * + * @param name the name for the argument value + * @return the OptionBuilder instance + */ + public static OptionBuilder withArgName( String name ) { + instance.argName = name; + return instance; + } + + /** + *

The next Option created will be required.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder isRequired( ) { + instance.required = true; + return instance; + } + + /** + *

The next Option created uses sep as a means to + * separate argument values.

+ * + * Example: + *
+     * Option opt = OptionBuilder.withValueSeparator( ':' )
+     *                           .create( 'D' );
+     *
+     * CommandLine line = parser.parse( args );
+     * String propertyName = opt.getValue( 0 );
+     * String propertyValue = opt.getValue( 1 );
+     * 
+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder withValueSeparator( char sep ) { + instance.valuesep = sep; + return instance; + } + + /** + *

The next Option created uses '=' as a means to + * separate argument values.

+ * + * Example: + *
+     * Option opt = OptionBuilder.withValueSeparator( )
+     *                           .create( 'D' );
+     *
+     * CommandLine line = parser.parse( args );
+     * String propertyName = opt.getValue( 0 );
+     * String propertyValue = opt.getValue( 1 );
+     * 
+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder withValueSeparator( ) { + instance.valuesep = '='; + return instance; + } + + /** + *

The next Option created will be required if required + * is true.

+ * + * @param required if true then the Option is required + * @return the OptionBuilder instance + */ + public static OptionBuilder isRequired( boolean required ) { + instance.required = required; + return instance; + } + + /** + *

The next Option created can have unlimited argument values.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArgs( ) { + instance.numberOfArgs = Option.UNLIMITED_VALUES; + return instance; + } + + /** + *

The next Option created can have num + * argument values.

+ * + * @param num the number of args that the option can have + * @return the OptionBuilder instance + */ + public static OptionBuilder hasArgs( int num ) { + instance.numberOfArgs = num; + return instance; + } + + /** + *

The next Option can have an optional argument.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasOptionalArg( ) { + instance.numberOfArgs = 1; + instance.optionalArg = true; + return instance; + } + + /** + *

The next Option can have an unlimited number of + * optional arguments.

+ * + * @return the OptionBuilder instance + */ + public static OptionBuilder hasOptionalArgs( ) { + instance.numberOfArgs = Option.UNLIMITED_VALUES; + instance.optionalArg = true; + return instance; + } + + /** + *

The next Option can have the specified number of + * optional arguments.

+ * + * @param numArgs - the maximum number of optional arguments + * the next Option created can have. + * @return the OptionBuilder instance + */ + public static OptionBuilder hasOptionalArgs( int numArgs ) { + instance.numberOfArgs = numArgs; + instance.optionalArg = true; + return instance; + } + + /** + *

The next Option created will have a value that will be an instance + * of type.

+ * + * @param type the type of the Options argument value + * @return the OptionBuilder instance + */ + public static OptionBuilder withType( Object type ) { + instance.type = type; + return instance; + } + + /** + *

The next Option created will have the specified description

+ * + * @param description a description of the Option's purpose + * @return the OptionBuilder instance + */ + public static OptionBuilder withDescription( String description ) { + instance.description = description; + return instance; + } + + /** + *

Create an Option using the current settings and with + * the specified Option char.

+ * + * @param opt the character representation of the Option + * @return the Option instance + * @throws IllegalArgumentException if opt is not + * a valid character. See Option. + */ + public static Option create( char opt ) + throws IllegalArgumentException + { + return create( String.valueOf( opt ) ); + } + + /** + *

Create an Option using the current settings

+ * + * @return the Option instance + * @throws IllegalArgumentException if longOpt has + * not been set. + */ + public static Option create() + throws IllegalArgumentException + { + if( longopt == null ) { + throw new IllegalArgumentException( "must specify longopt" ); + } + + return create( " " ); + } + + /** + *

Create an Option using the current settings and with + * the specified Option char.

+ * + * @param opt the java.lang.String representation + * of the Option + * @return the Option instance + * @throws IllegalArgumentException if opt is not + * a valid character. See Option. + */ + public static Option create( String opt ) + throws IllegalArgumentException + { + // create the option + Option option = new Option( opt, description ); + + // set the option properties + option.setLongOpt( longopt ); + option.setRequired( required ); + option.setOptionalArg( optionalArg ); + option.setArgs( numberOfArgs ); + option.setType( type ); + option.setValueSeparator( valuesep ); + option.setArgName( argName ); + // reset the OptionBuilder properties + instance.reset(); + + // return the Option instance + return option; + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/OptionGroup.java b/installer/src/java/org/apache/commons/cli/OptionGroup.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/OptionGroup.java @@ -0,0 +1,187 @@ +/* + * $Header$ + * $Revision: 2690 $ + * $Date: 2006-03-25 23:39:57 -0800 (Sat, 25 Mar 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * A group of mutually exclusive options. + * @author John Keyes ( john at integralsource.com ) + * @version $Revision: 2690 $ + */ +public class OptionGroup { + + /** hold the options in added order */ + private List optionList = new ArrayList(); + + /** the name of the selected option */ + private String selected; + + /** specified whether this group is required */ + private boolean required; + + /** + * add opt to this group + * + * @param opt the option to add to this group + * @return this option group with opt added + */ + public OptionGroup addOption(Option opt) { + optionList.add(opt); + return this; + } + + /** + * @return the names of the options in this group as a + * Collection + */ + public Collection getNames() { + List namesList = new ArrayList(); + Iterator optionsIterator = optionList.iterator(); + while (optionsIterator.hasNext()) { + Option option = (Option) optionsIterator.next(); + namesList.add("-" + option.getOpt()); + } + return namesList; + } + + /** + * @return the options in this group as a Collection + */ + public Collection getOptions() { + return optionList; + } + + /** + * set the selected option of this group to name. + * @param opt the option that is selected + * @throws AlreadySelectedException if an option from this group has + * already been selected. + */ + public void setSelected(Option opt) throws AlreadySelectedException { + // if no option has already been selected or the + // same option is being reselected then set the + // selected member variable + + if ( this.selected == null || this.selected.equals( opt.getOpt() ) ) { + this.selected = opt.getOpt(); + } + else { + throw new AlreadySelectedException( "an option from this group has " + + "already been selected: '" + + selected + "'"); + } + } + + /** + * @return the selected option name + */ + public String getSelected() { + return selected; + } + + /** + * @param required specifies if this group is required + */ + public void setRequired( boolean required ) { + this.required = required; + } + + /** + * Returns whether this option group is required. + * + * @returns whether this option group is required + */ + public boolean isRequired() { + return this.required; + } + + /** + *

Returns the stringified version of this OptionGroup.

+ * @return the stringified representation of this group + */ + public String toString() { + StringBuffer buff = new StringBuffer(); + + Iterator iter = getOptions().iterator(); + + buff.append( "[" ); + while( iter.hasNext() ) { + Option option = (Option)iter.next(); + + buff.append( "-" ); + buff.append( option.getOpt() ); + buff.append( " " ); + buff.append( option.getDescription( ) ); + + if( iter.hasNext() ) { + buff.append( ", " ); + } + } + buff.append( "]" ); + + return buff.toString(); + } +} diff --git a/installer/src/java/org/apache/commons/cli/Options.java b/installer/src/java/org/apache/commons/cli/Options.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/Options.java @@ -0,0 +1,331 @@ +/* + * $Header$ + * $Revision: 2694 $ + * $Date: 2006-03-27 11:45:13 -0800 (Mon, 27 Mar 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/**

Main entry-point into the library.

+ * + *

Options represents a collection of {@link Option} objects, which + * describe the possible options for a command-line.

+ * + *

It may flexibly parse long and short options, with or without + * values. Additionally, it may parse only a portion of a commandline, + * allowing for flexible multi-stage parsing.

+ * + * @see org.apache.commons.cli.CommandLine + * + * @author bob mcwhirter (bob @ werken.com) + * @author James Strachan + * @version $Revision: 2694 $ + */ +public class Options { + + /** a map of the options with the character key */ + private Map shortOpts = new HashMap(); + + /** a map of the options with the long key */ + private Map longOpts = new HashMap(); + + /** a list of the required options */ + private List requiredOpts = new ArrayList(); + + /** a map of the option groups */ + private Map optionGroups = new HashMap(); + + /** a list of all options, in sequence of addition, only in use when isSortAsAdded() */ + private List _addedOpts = new ArrayList(); + + /** flag for the sort behaviour */ + private boolean _sortAsAdded; + + /** + * Construct a new Options descriptor + */ + public Options() { + setSortAsAdded(false); + } + + /** + * Set the sort sequence: true means in sequence of addition, false will use the + * default (undefined) sorting + */ + public void setSortAsAdded(boolean sortAsAdded) { + _sortAsAdded = sortAsAdded; + } + + /** + *

Add the specified option group.

+ * + * @param group the OptionGroup that is to be added + * @return the resulting Options instance + */ + public Options addOptionGroup( OptionGroup group ) { + Iterator options = group.getOptions().iterator(); + + if( group.isRequired() ) { + requiredOpts.add( group ); + } + + while( options.hasNext() ) { + Option option = (Option)options.next(); + // an Option cannot be required if it is in an + // OptionGroup, either the group is required or + // nothing is required + option.setRequired( false ); + addOption( option ); + + optionGroups.put( option.getOpt(), group ); + } + + return this; + } + + /**

Add an option that only contains a short-name

+ *

It may be specified as requiring an argument.

+ * + * @param opt Short single-character name of the option. + * @param hasArg flag signally if an argument is required after this option + * @param description Self-documenting description + * @return the resulting Options instance + */ + public Options addOption(String opt, boolean hasArg, String description) { + addOption( opt, null, hasArg, description ); + return this; + } + + /**

Add an option that contains a short-name and a long-name

+ *

It may be specified as requiring an argument.

+ * + * @param opt Short single-character name of the option. + * @param longOpt Long multi-character name of the option. + * @param hasArg flag signally if an argument is required after this option + * @param description Self-documenting description + * @return the resulting Options instance + */ + public Options addOption(String opt, String longOpt, boolean hasArg, String description) { + addOption( new Option( opt, longOpt, hasArg, description ) ); + return this; + } + + /** + *

Adds an option instance

+ * + * @param opt the option that is to be added + * @return the resulting Options instance + */ + public Options addOption(Option opt) { + String shortOpt = "-" + opt.getOpt(); + + // add it to the long option list + if ( opt.hasLongOpt() ) { + longOpts.put( "--" + opt.getLongOpt(), opt ); + } + + // if the option is required add it to the required list + if ( opt.isRequired() ) { + requiredOpts.add( shortOpt ); + } + + shortOpts.put( shortOpt, opt ); + + if (isSortAsAdded()) { + _addedOpts.add(opt); + } + + return this; + } + + /**

Retrieve a read-only list of options in this set

+ * + * @return read-only Collection of {@link Option} objects in this descriptor + */ + public Collection getOptions() { + if (isSortAsAdded()) { + return _addedOpts; + } else { + List opts = new ArrayList(shortOpts.values()); + + // now look through the long opts to see if there are any Long-opt + // only options + Iterator iter = longOpts.values().iterator(); + while (iter.hasNext()) { + Object item = iter.next(); + if (!opts.contains(item)) { + opts.add(item); + } + } + return Collections.unmodifiableCollection(opts); + } + } + + /** + * @return true if options should be sorted in sequence of addition. + */ + public boolean isSortAsAdded() { + return _sortAsAdded; + } + + /** + *

Returns the Options for use by the HelpFormatter.

+ * + * @return the List of Options + */ + List helpOptions() { + if (isSortAsAdded()) { + return _addedOpts; + } else { + return new ArrayList(shortOpts.values()); + } + } + + /**

Returns the required options as a + * java.util.Collection.

+ * + * @return Collection of required options + */ + public List getRequiredOptions() { + return requiredOpts; + } + + /**

Retrieve the named {@link Option}

+ * + * @param opt short or long name of the {@link Option} + * @return the option represented by opt + */ + public Option getOption( String opt ) { + + Option option = null; + + // short option + if( opt.length() == 1 ) { + option = (Option)shortOpts.get( "-" + opt ); + } + // long option + else if( opt.startsWith( "--" ) ) { + option = (Option)longOpts.get( opt ); + } + // a just-in-case + else { + option = (Option)shortOpts.get( opt ); + } + + return (option == null) ? null : (Option)option.clone(); + } + + /** + *

Returns whether the named {@link Option} is a member + * of this {@link Options}

+ * + * @param opt short or long name of the {@link Option} + * @return true if the named {@link Option} is a member + * of this {@link Options} + */ + public boolean hasOption( String opt ) { + + // short option + if( opt.length() == 1 ) { + return shortOpts.containsKey( "-" + opt ); + } + // long option + else if( opt.startsWith( "--" ) ) { + return longOpts.containsKey( opt ); + } + // a just-in-case + else { + return shortOpts.containsKey( opt ); + } + } + + /**

Returns the OptionGroup the opt + * belongs to.

+ * @param opt the option whose OptionGroup is being queried. + * + * @return the OptionGroup if opt is part + * of an OptionGroup, otherwise return null + */ + public OptionGroup getOptionGroup( Option opt ) { + return (OptionGroup)optionGroups.get( opt.getOpt() ); + } + + /**

Dump state, suitable for debugging.

+ * + * @return Stringified form of this object + */ + public String toString() { + StringBuffer buf = new StringBuffer(); + + buf.append("[ Options: [ short "); + buf.append( shortOpts.toString() ); + buf.append( " ] [ long " ); + buf.append( longOpts ); + buf.append( " ]"); + + return buf.toString(); + } +} diff --git a/installer/src/java/org/apache/commons/cli/ParseException.java b/installer/src/java/org/apache/commons/cli/ParseException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/ParseException.java @@ -0,0 +1,82 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Base for Exceptions thrown during parsing of a command-line.

+ * + * @author bob mcwhirter (bob @ werken.com) + * @version $Revision: 2662 $ + */ +public class ParseException extends Exception +{ + + /** + *

Construct a new ParseException + * with the specified detail message.

+ * + * @param message the detail message + */ + public ParseException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/apache/commons/cli/Parser.java b/installer/src/java/org/apache/commons/cli/Parser.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/Parser.java @@ -0,0 +1,282 @@ +/* + * $Header$ + * $Revision: 2665 $ + * $Date: 2006-02-18 14:24:36 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + *

Parser creates {@link CommandLine}s.

+ * + * @author John Keyes (john at integralsource.com) + * @see Parser + * @version $Revision: 2665 $ + */ +public abstract class Parser implements CommandLineParser { + + /** commandline instance */ + private CommandLine cmd; + /** current Options */ + private Options options; + /** list of required options strings */ + private List requiredOptions; + + /** + *

Subclasses must implement this method to reduce + * the arguments that have been passed to the parse + * method.

+ * + * @param opts The Options to parse the arguments by. + * @param args The arguments that have to be flattened. + * @param stopAtNonOption specifies whether to stop + * flattening when a non option has been encountered + * @return a String array of the flattened arguments + */ + abstract protected String[] flatten( Options opts, + String[] arguments, + boolean stopAtNonOption ); + + /** + *

Parses the specified arguments + * based on the specifed {@link Options}.

+ * + * @param options the Options + * @param arguments the arguments + * @return the CommandLine + * @throws ParseException if an error occurs when parsing the + * arguments. + */ + public CommandLine parse( Options options, String[] arguments ) + throws ParseException + { + return parse( options, arguments, false ); + } + + /** + *

Parses the specified arguments + * based on the specifed {@link Options}.

+ * + * @param options the Options + * @param arguments the arguments + * @param stopAtNonOption specifies whether to stop + * interpreting the arguments when a non option has + * been encountered and to add them to the CommandLines + * args list. + * @return the CommandLine + * @throws ParseException if an error occurs when parsing the + * arguments. + */ + public CommandLine parse( Options opts, + String[] arguments, + boolean stopAtNonOption ) + throws ParseException + { + // initialise members + options = opts; + requiredOptions = options.getRequiredOptions(); + cmd = new CommandLine(); + + boolean eatTheRest = false; + + List tokenList = Arrays.asList( flatten( opts, arguments, stopAtNonOption ) ); + ListIterator iterator = tokenList.listIterator(); + + // process each flattened token + while( iterator.hasNext() ) { + String t = (String)iterator.next(); + + // the value is the double-dash + if( "--".equals( t ) ) { + eatTheRest = true; + } + // the value is a single dash + else if( "-".equals( t ) ) { + if( stopAtNonOption ) { + eatTheRest = true; + } + else { + cmd.addArg(t ); + } + } + // the value is an option + else if( t.startsWith( "-" ) ) { + if ( stopAtNonOption && !options.hasOption( t ) ) { + eatTheRest = true; + cmd.addArg( t ); + } + else { + processOption( t, iterator ); + } + } + // the value is an argument + else { + cmd.addArg( t ); + if( stopAtNonOption ) { + eatTheRest = true; + } + } + + // eat the remaining tokens + if( eatTheRest ) { + while( iterator.hasNext() ) { + String str = (String)iterator.next(); + // ensure only one double-dash is added + if( !"--".equals( str ) ) { + cmd.addArg( str ); + } + } + } + } + checkRequiredOptions(); + return cmd; + } + + /** + *

Throws a {@link MissingOptionException} if all of the + * required options are no present.

+ */ + private void checkRequiredOptions() + throws MissingOptionException + { + + // if there are required options that have not been + // processsed + if( requiredOptions.size() > 0 ) { + Iterator iter = requiredOptions.iterator(); + StringBuffer buff = new StringBuffer(); + + // loop through the required options + while( iter.hasNext() ) { + buff.append( iter.next() ); + } + + throw new MissingOptionException( buff.toString() ); + } + } + + public void processArgs( Option opt, ListIterator iter ) + throws ParseException + { + // loop until an option is found + while( iter.hasNext() ) { + String var = (String)iter.next(); + + // found an Option + if( options.hasOption( var ) ) { + iter.previous(); + break; + } + // found a value + else if( !opt.addValue( var ) ) { + iter.previous(); + break; + } + } + + if( opt.getValues() == null && !opt.hasOptionalArg() ) { + throw new MissingArgumentException( "no argument for option: " + opt.getOpt() + " / " + opt.getLongOpt() ); + } + } + + private void processOption( String arg, ListIterator iter ) + throws ParseException + { + // get the option represented by arg + Option opt = null; + + boolean hasOption = options.hasOption( arg ); + + // if there is no option throw an UnrecognisedOptionException + if( !hasOption ) { + throw new UnrecognizedOptionException("Unrecognized option: " + arg); + } + else { + opt = (Option) options.getOption( arg ); + } + + // if the option is a required option remove the option from + // the requiredOptions list + if ( opt.isRequired() ) { + requiredOptions.remove( "-" + opt.getOpt() ); + } + + // if the option is in an OptionGroup make that option the selected + // option of the group + if ( options.getOptionGroup( opt ) != null ) { + OptionGroup group = ( OptionGroup ) options.getOptionGroup( opt ); + if( group.isRequired() ) { + requiredOptions.remove( group ); + } + group.setSelected( opt ); + } + + // if the option takes an argument value + if ( opt.hasArg() ) { + processArgs( opt, iter ); + } + + // set the option on the command line + cmd.addOption( opt ); + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java b/installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/PatternOptionBuilder.java @@ -0,0 +1,204 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + * Allows Options to be created from a single String. + * + * + * @author Henri Yandell (bayard @ generationjava.com) + * @version $Revision: 2662 $ + */ +public class PatternOptionBuilder { + + /// TODO: These need to break out to OptionType and also to be pluggable. + + /** String class */ + public static final Class STRING_VALUE = java.lang.String.class; + /** Object class */ + public static final Class OBJECT_VALUE = java.lang.Object.class; + /** Number class */ + public static final Class NUMBER_VALUE = java.lang.Number.class; + /** Date class */ + public static final Class DATE_VALUE = java.util.Date.class; + /** Class class */ + public static final Class CLASS_VALUE = java.lang.Class.class; + +/// can we do this one?? +// is meant to check that the file exists, else it errors. +// ie) it's for reading not writing. + /** FileInputStream class */ + public static final Class EXISTING_FILE_VALUE = java.io.FileInputStream.class; + /** File class */ + public static final Class FILE_VALUE = java.io.File.class; + /** File array class */ + public static final Class FILES_VALUE = java.io.File[].class; + /** URL class */ + public static final Class URL_VALUE = java.net.URL.class; + + /** + *

Retrieve the class that ch represents.

+ * + * @param ch the specified character + * @return The class that ch represents + */ + public static Object getValueClass(char ch) { + if (ch == '@') { + return PatternOptionBuilder.OBJECT_VALUE; + } else if (ch == ':') { + return PatternOptionBuilder.STRING_VALUE; + } else if (ch == '%') { + return PatternOptionBuilder.NUMBER_VALUE; + } else if (ch == '+') { + return PatternOptionBuilder.CLASS_VALUE; + } else if (ch == '#') { + return PatternOptionBuilder.DATE_VALUE; + } else if (ch == '<') { + return PatternOptionBuilder.EXISTING_FILE_VALUE; + } else if (ch == '>') { + return PatternOptionBuilder.FILE_VALUE; + } else if (ch == '*') { + return PatternOptionBuilder.FILES_VALUE; + } else if (ch == '/') { + return PatternOptionBuilder.URL_VALUE; + } + return null; + } + + /** + *

Returns whether ch is a value code, i.e. + * whether it represents a class in a pattern.

+ * + * @param ch the specified character + * @return true if ch is a value code, otherwise false. + */ + public static boolean isValueCode(char ch) { + if( (ch != '@') && + (ch != ':') && + (ch != '%') && + (ch != '+') && + (ch != '#') && + (ch != '<') && + (ch != '>') && + (ch != '*') && + (ch != '/') + ) + { + return false; + } + return true; + } + + /** + *

Returns the {@link Options} instance represented by + * pattern.

+ * + * @param pattern the pattern string + * @return The {@link Options} instance + */ + public static Options parsePattern(String pattern) { + int sz = pattern.length(); + + char opt = ' '; + char ch = ' '; + boolean required = false; + Object type = null; + + Options options = new Options(); + + for(int i=0; i. + * + */ +package org.apache.commons.cli; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; + +/** + * The class PosixParser provides an implementation of the + * {@link Parser#flatten(Options,String[],boolean) flatten} method. + * + * @author John Keyes (john at integralsource.com) + * @see Parser + * @version $Revision: 2833 $ + */ +public class PosixParser extends Parser { + + /** holder for flattened tokens */ + private ArrayList tokens = new ArrayList(); + /** specifies if bursting should continue */ + private boolean eatTheRest; + /** holder for the current option */ + private Option currentOption; + /** the command line Options */ + private Options options; + + /** + *

Resets the members to their original state i.e. remove + * all of tokens entries, set eatTheRest + * to false and set currentOption to null.

+ */ + private void init() { + eatTheRest = false; + tokens.clear(); + currentOption = null; + } + + /** + *

An implementation of {@link Parser}'s abstract + * {@link Parser#flatten(Options,String[],boolean) flatten} method.

+ * + *

The following are the rules used by this flatten method. + *

    + *
  1. if stopAtNonOption is true then do not + * burst anymore of arguments entries, just add each + * successive entry without further processing. Otherwise, ignore + * stopAtNonOption.
  2. + *
  3. if the current arguments entry is "--" + * just add the entry to the list of processed tokens
  4. + *
  5. if the current arguments entry is "-" + * just add the entry to the list of processed tokens
  6. + *
  7. if the current arguments entry is two characters + * in length and the first character is "-" then check if this + * is a valid {@link Option} id. If it is a valid id, then add the + * entry to the list of processed tokens and set the current {@link Option} + * member. If it is not a valid id and stopAtNonOption + * is true, then the remaining entries are copied to the list of + * processed tokens. Otherwise, the current entry is ignored.
  8. + *
  9. if the current arguments entry is more than two + * characters in length and the first character is "-" then + * we need to burst the entry to determine its constituents. For more + * information on the bursting algorithm see + * {@link PosixParser#burstToken( String, boolean) burstToken}.
  10. + *
  11. if the current arguments entry is not handled + * by any of the previous rules, then the entry is added to the list + * of processed tokens.
  12. + *
+ *

+ * + * @param options The command line {@link Options} + * @param arguments The command line arguments to be parsed + * @param stopAtNonOption Specifies whether to stop flattening + * when an non option is found. + * @return The flattened arguments String array. + */ + protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption) { + init(); + this.options = options; + + // an iterator for the command line tokens + Iterator iter = Arrays.asList(arguments).iterator(); + String token = null; + + // process each command line token + while (iter.hasNext()) { + + // get the next command line token + token = (String) iter.next(); + + // handle SPECIAL TOKEN + if (token.startsWith("--")) { + processLongOptionToken(token, stopAtNonOption); + } else if ("-".equals(token)) { + // single hyphen + processSingleHyphen(token); + } else if (token.startsWith("-")) { + int tokenLength = token.length(); + if (tokenLength == 2) { + processOptionToken(token, stopAtNonOption); + } else { + boolean burst = true; + if (token.length() > 2) { + // check for misspelled long option + String longOptionCandidate = "-" + token; + if (this.options.hasOption(longOptionCandidate)) { + tokens.add(token); + burst = false; + } + } + if (burst) { + burstToken(token, stopAtNonOption); + } + } + } else { + if (stopAtNonOption) { + process(token); + } else { + tokens.add(token); + } + } + + gobble(iter); + } + + return (String[]) tokens.toArray(new String[] {}); + } + + /** + *

Adds the remaining tokens to the processed tokens list.

+ * + * @param iter An iterator over the remaining tokens + */ + private void gobble( Iterator iter ) { + if( eatTheRest ) { + while( iter.hasNext() ) { + tokens.add( iter.next() ); + } + } + } + + /** + *

If there is a current option and it can have an argument + * value then add the token to the processed tokens list and + * set the current option to null.

+ *

If there is a current option and it can have argument + * values then add the token to the processed tokens list.

+ *

If there is not a current option add the special token + * "--" and the current value to the processed + * tokens list. The add all the remaining argument + * values to the processed tokens list.

+ * + * @param value The current token + */ + private void process( String value ) { + if( currentOption != null && currentOption.hasArg() ) { + if( currentOption.hasArg() ) { + tokens.add( value ); + currentOption = null; + } + else if (currentOption.hasArgs() ) { + tokens.add( value ); + } + } + else { + eatTheRest = true; + tokens.add( "--" ); + tokens.add( value ); + } + } + + /** + *

If it is a hyphen then add the hyphen directly to + * the processed tokens list.

+ * + * @param hyphen The hyphen token + */ + private void processSingleHyphen( String hyphen ) { + tokens.add( hyphen ); + } + + /** + *

If an {@link Option} exists for token then + * set the current option and add the token to the processed + * list.

+ *

If an {@link Option} does not exist and stopAtNonOption + * is set then ignore the current token and add the remaining tokens + * to the processed tokens list directly.

+ * + * @param token The current option token + * @param stopAtNonOption Specifies whether flattening should halt + * at the first non option. + */ + private void processOptionToken( String token, boolean stopAtNonOption ) { + if( this.options.hasOption( token ) ) { + currentOption = this.options.getOption( token ); + tokens.add( token ); + } else { + if( stopAtNonOption ) { + eatTheRest = true; + } else { + tokens.add(token); + } + } + } + + /** + * same stop logic as for single hyphen options + * @param longToken + * @param stopAtNonOption + */ + private void processLongOptionToken(String longToken, boolean stopAtNonOption ) { + String token; + String value = null; + if( longToken.indexOf( '=' ) != -1 ) { + token = longToken.substring( 0, longToken.indexOf( '=' )); + value = longToken.substring( longToken.indexOf( '=' ) + 1, longToken.length() ); + } else { + token = longToken; + } + if( this.options.hasOption(token)) { + tokens.add(token); + if( value != null) { + tokens.add(value); + } + } else { + if(stopAtNonOption) { + eatTheRest = true; + } else { + tokens.add(token); + if( value != null) { + tokens.add(value); + } + } + } + } + + + /** + *

Breaks token into its constituent parts + * using the following algorithm. + *

    + *
  • ignore the first character ("-" )
  • + *
  • foreach remaining character check if an {@link Option} + * exists with that id.
  • + *
  • if an {@link Option} does exist then add that character + * prepended with "-" to the list of processed tokens.
  • + *
  • if the {@link Option} can have an argument value and there + * are remaining characters in the token then add the remaining + * characters as a token to the list of processed tokens.
  • + *
  • if an {@link Option} does NOT exist AND + * stopAtNonOption IS set then add the special token + * "--" followed by the remaining characters and also + * the remaining tokens directly to the processed tokens list.
  • + *
  • if an {@link Option} does NOT exist AND + * stopAtNonOption IS NOT set then add that + * character prepended with "-".
  • + *
+ *

+ */ + protected void burstToken( String token, boolean stopAtNonOption ) { + int tokenLength = token.length(); + + for( int i = 1; i < tokenLength; i++) { + String ch = String.valueOf( token.charAt( i ) ); + boolean hasOption = options.hasOption( ch ); + + if( hasOption ) { + tokens.add( "-" + ch ); + currentOption = options.getOption( ch ); + if( currentOption.hasArg() && token.length()!=i+1 ) { + tokens.add( token.substring( i+1 ) ); + break; + } + } + else if( stopAtNonOption ) { + process( token.substring( i ) ); + } + else { + tokens.add( "-" + ch ); + } + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/apache/commons/cli/TypeHandler.java b/installer/src/java/org/apache/commons/cli/TypeHandler.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/TypeHandler.java @@ -0,0 +1,252 @@ +/* + * $Header$ + * $Revision: 2664 $ + * $Date: 2006-02-18 14:20:35 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +import java.io.File; +import java.math.BigDecimal; +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Date; + +/** + * This is a temporary implementation. TypeHandler will handle the + * pluggableness of OptionTypes and it will direct all of these types + * of conversion functionalities to ConvertUtils component in Commons + * alreayd. BeanUtils I think. + * + * @author Henri Yandell (bayard @ generationjava.com) + * @version $Revision: 2664 $ + */ +public class TypeHandler { + + /** + *

Returns the Object of type obj + * with the value of str.

+ * + * @param str the command line value + * @param obj the type of argument + * @return The instance of obj initialised with + * the value of str. + */ + public static Object createValue(String str, Object obj) { + return createValue(str, (Class)obj); + } + + /** + *

Returns the Object of type clazz + * with the value of str.

+ * + * @param str the command line value + * @param clazz the type of argument + * @return The instance of clazz initialised with + * the value of str. + */ + public static Object createValue(String str, Class clazz) { + if( PatternOptionBuilder.STRING_VALUE == clazz) { + return str; + } else + if( PatternOptionBuilder.OBJECT_VALUE == clazz) { + return createObject(str); + } else + if( PatternOptionBuilder.NUMBER_VALUE == clazz) { + return createNumber(str); + } else + if( PatternOptionBuilder.DATE_VALUE == clazz) { + return createDate(str); + } else + if( PatternOptionBuilder.CLASS_VALUE == clazz) { + return createClass(str); + } else + if( PatternOptionBuilder.FILE_VALUE == clazz) { + return createFile(str); + } else + if( PatternOptionBuilder.EXISTING_FILE_VALUE == clazz) { + return createFile(str); + } else + if( PatternOptionBuilder.FILES_VALUE == clazz) { + return createFiles(str); + } else + if( PatternOptionBuilder.URL_VALUE == clazz) { + return createURL(str); + } else { + return null; + } + } + + /** + *

Create an Object from the classname and empty constructor.

+ * + * @param str the argument value + * @return the initialised object, or null if it couldn't create the Object. + */ + public static Object createObject(String str) { + Class cl = null; + try { + cl = Class.forName(str); + } catch (ClassNotFoundException cnfe) { + System.err.println("Unable to find: "+str); + return null; + } + + Object instance = null; + + try { + instance = cl.newInstance(); + } catch (InstantiationException cnfe) { + System.err.println("InstantiationException; Unable to create: "+str); + return null; + } + catch (IllegalAccessException cnfe) { + System.err.println("IllegalAccessException; Unable to create: "+str); + return null; + } + + return instance; + } + + /** + *

Create a number from a String.

+ * + * @param str the value + * @return the number represented by str, if str + * is not a number, null is returned. + */ + public static Number createNumber(String str) { + // Needs to be able to create + try { + // do searching for decimal point etc, but atm just make an Integer + return new BigDecimal(str); + } catch (NumberFormatException nfe) { + System.err.println(nfe.getMessage()); + return null; + } + } + + /** + *

Returns the class whose name is str.

+ * + * @param str the class name + * @return The class if it is found, otherwise return null + */ + public static Class createClass(String str) { + try { + return Class.forName(str); + } catch (ClassNotFoundException cnfe) { + System.err.println("Unable to find: "+str); + return null; + } + } + + /** + *

Returns the date represented by str.

+ * + * @param str the date string + * @return The date if str is a valid date string, + * otherwise return null. + */ + public static Date createDate(String str) { + Date date = null; + if(date == null) { + System.err.println("Unable to parse: "+str); + } + return date; + } + + /** + *

Returns the URL represented by str.

+ * + * @param str the URL string + * @return The URL is str is well-formed, otherwise + * return null. + */ + public static URL createURL(String str) { + try { + return new URL(str); + } catch (MalformedURLException mue) { + System.err.println("Unable to parse: "+str); + return null; + } + } + + /** + *

Returns the File represented by str.

+ * + * @param str the File location + * @return The file represented by str. + */ + public static File createFile(String str) { + return new File(str); + } + + /** + *

Returns the File[] represented by str.

+ * + * @param str the paths to the files + * @return The File[] represented by str. + */ + public static File[] createFiles(String str) { +// to implement/port: +// return FileW.findFiles(str); + return null; + } + +} diff --git a/installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java b/installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/apache/commons/cli/UnrecognizedOptionException.java @@ -0,0 +1,82 @@ +/* + * $Header$ + * $Revision: 2662 $ + * $Date: 2006-02-18 06:20:33 -0800 (Sat, 18 Feb 2006) $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache at apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.commons.cli; + +/** + *

Exception thrown during parsing signalling an unrecognized + * option was seen.

+ * + * @author bob mcwhiter (bob @ werken.com) + * @version $Revision: 2662 $ + */ +public class UnrecognizedOptionException extends ParseException { + + /** + *

Construct a new UnrecognizedArgumentException + * with the specified detail message.

+ * + * @param message the detail message + */ + public UnrecognizedOptionException( String message ) { + super( message ); + } +} diff --git a/installer/src/java/org/python/util/install/AbstractWizard.java b/installer/src/java/org/python/util/install/AbstractWizard.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizard.java @@ -0,0 +1,438 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Cursor; +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.HeadlessException; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.Iterator; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JSeparator; + +public abstract class AbstractWizard extends JDialog implements ValidationListener { + + private class WizardGlassPane extends JPanel implements MouseListener, KeyListener { + WizardGlassPane() { + super(); + setOpaque(false); + addMouseListener(this); + addKeyListener(this); + } + + public void keyPressed(KeyEvent e) { + } + + public void keyReleased(KeyEvent e) { + } + + public void keyTyped(KeyEvent e) { + } + + public void mouseClicked(MouseEvent e) { + } + + public void mouseEntered(MouseEvent e) { + } + + public void mouseExited(MouseEvent e) { + } + + public void mousePressed(MouseEvent e) { + } + + public void mouseReleased(MouseEvent e) { + } + } + + private AbstractWizardPage _activePage = null; + private JPanel _buttonPanel; + private JSeparator _buttonSeparator; + private Action _cancelAction; + private JButton _cancelButton; + private CardLayout _cards; + private JPanel _content; + private WizardGlassPane _glassPane; + private AbstractWizardHeader _header; + private boolean _headerVisible = false; + private ArrayList _listeners; + private Action _nextAction; + private JButton _nextButton; + private ArrayList _pages; + private Action _previousAction; + private JButton _previousButton; + + public AbstractWizard() { + super(); + initWindow(); + initActions(); + initComponents(); + } + + public AbstractWizard(Dialog parent) throws HeadlessException { + super(parent); + initWindow(); + initActions(); + initComponents(); + } + + public AbstractWizard(Frame parent) throws HeadlessException { + super(parent); + initWindow(); + initActions(); + initComponents(); + } + + public final void addPage(AbstractWizardPage page) { + if (_pages == null) + _pages = new ArrayList(); + if (page == null || _pages.contains(page)) + return; + _pages.add(page); + page.setWizard(this); + int number = _pages.indexOf(page); + _content.add(page, "page" + number); + } + + public final void addWizardListener(WizardListener listener) { + if (listener == null) + return; + if (_listeners == null) + _listeners = new ArrayList(5); + if (_listeners.contains(listener)) + return; + _listeners.add(listener); + } + + private void cancel() { + fireCancelEvent(); + setVisible(false); + } + + /** + * @return whether the wizard finished succesfully + */ + protected abstract boolean finish(); + + private void fireCancelEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardCancelled(event); + } + } + + private void fireFinishedEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardFinished(event); + } + } + + private void fireNextEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardNext(event); + } + } + + private void firePreviousEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardPrevious(event); + } + } + + private void fireStartedEvent() { + if (_listeners == null || _listeners.isEmpty()) + return; + WizardEvent event = new WizardEvent(this); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + WizardListener listener = (WizardListener) it.next(); + listener.wizardStarted(event); + } + } + + /** + * @return the String that will appear on the Cancel button + */ + protected abstract String getCancelString(); + + /** + * @return the String that will appear on the Finish button + */ + protected abstract String getFinishString(); + + /** + * @return the wizard header panel + */ + public AbstractWizardHeader getHeader() { + return _header; + } + + /** + * @return the String that will appear on the Next button + */ + protected abstract String getNextString(); + + /** + * @return the String that will appear on the Previous button + */ + protected abstract String getPreviousString(); + + /** + * usually only called from the WizardValidator, after validation succeeds + * + * validation always occurs when the 'next' or 'finish' button was clicked, so when the validation succeeds, the + * wizard can go to the next page + */ + public final void gotoNextPage() { + next(); + } + + private void initActions() { + _nextAction = new AbstractAction(getNextString()) { + public void actionPerformed(ActionEvent e) { + tryNext(); + } + }; + _previousAction = new AbstractAction(getPreviousString()) { + public void actionPerformed(ActionEvent e) { + previous(); + } + }; + _cancelAction = new AbstractAction(getCancelString()) { + public void actionPerformed(ActionEvent e) { + cancel(); + } + }; + } + + private void initComponents() { + _pages = new ArrayList(); + getContentPane().setLayout(new BorderLayout(0, 0)); + _header = new WizardHeader(); + getContentPane().add(_header, BorderLayout.NORTH); + _content = new JPanel(); + _cards = new CardLayout(); + _content.setLayout(_cards); + + getContentPane().add(_content, BorderLayout.CENTER); // was: WEST + + _buttonPanel = new JPanel(); + _buttonSeparator = new JSeparator(); + _cancelButton = new JButton(); + _previousButton = new JButton(); + _nextButton = new JButton(); + _cancelButton.setAction(_cancelAction); + _previousButton.setAction(_previousAction); + _nextButton.setAction(_nextAction); + GridBagConstraints gridBagConstraints; + _buttonPanel.setLayout(new GridBagLayout()); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 3; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = new Insets(1, 1, 1, 1); + gridBagConstraints.weightx = 1.0; + _buttonPanel.add(_buttonSeparator, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + _buttonPanel.add(_cancelButton, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + _buttonPanel.add(_previousButton, gridBagConstraints); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + _buttonPanel.add(_nextButton, gridBagConstraints); + getContentPane().add(_buttonPanel, BorderLayout.SOUTH); + } + + private void initWindow() { + _glassPane = new WizardGlassPane(); + setGlassPane(_glassPane); + } + + /** + * @return whether the wizard header is visible + */ + public final boolean isHeaderVisible() { + return _headerVisible; + } + + /** + * lock the wizard dialog, preventing any user input + */ + public final void lock() { + _glassPane.setVisible(true); + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + } + + private void next() { + if (_activePage == null) + return; + _activePage.passivate(); + int activeIndex = _pages.indexOf(_activePage); + int nextIndex = activeIndex + 1; + if (nextIndex >= _pages.size()) { + tryFinish(); + return; + } else { + _activePage = (AbstractWizardPage) _pages.get(nextIndex); + showActivePage(); + } + fireNextEvent(); + } + + private void previous() { + if (_activePage == null) + return; + _activePage.passivate(); + int activeIndex = _pages.indexOf(_activePage); + int previousIndex = activeIndex - 1; + if (previousIndex < 0) + return; + else { + _activePage = (AbstractWizardPage) _pages.get(previousIndex); + showActivePage(); + } + firePreviousEvent(); + } + + public final void removeWizardListener(WizardListener listener) { + if (listener == null || _listeners == null || !_listeners.contains(listener)) + return; + _listeners.remove(listener); + } + + /** + * @param header the wizard header panel + */ + public void setHeader(AbstractWizardHeader header) { + if (this._header != null) { + getContentPane().remove(header); + } + this._header = header; + if (this._header != null) { + getContentPane().add(header, BorderLayout.NORTH); + } + } + + /** + * @param visible show the header in this wizard? + */ + public final void setHeaderVisible(boolean visible) { + _headerVisible = visible; + if (_header != null) + _header.setVisible(visible); + } + + @Override + public void setVisible(boolean visible) { + if (visible) { + fireStartedEvent(); + if (_pages.size() > 0) { + _activePage = (AbstractWizardPage) _pages.get(0); + showActivePage(); + } + } + super.setVisible(visible); + } + + private void showActivePage() { + if (_activePage == null) + return; + int number = _pages.indexOf(_activePage); + // show the active page + _cards.show(_content, "page" + number); + // update the wizard header + if (_header != null) { + _header.setTitle(_activePage.getTitle()); + _header.setDescription(_activePage.getDescription()); + _header.setIcon(_activePage.getIcon()); + } + // set visibility and localized text of buttons + if (number == 0) { + _previousButton.setVisible(false); + } else { + _previousButton.setVisible(_activePage.isPreviousVisible()); + } + _previousAction.putValue(Action.NAME, getPreviousString()); + _cancelButton.setVisible(_activePage.isCancelVisible()); + _cancelAction.putValue(Action.NAME, getCancelString()); + _nextButton.setVisible(_activePage.isNextVisible()); + _nextAction.putValue(Action.NAME, getNextString()); + if (number + 1 == _pages.size()) { + _nextAction.putValue(Action.NAME, getFinishString()); + } else { + _nextAction.putValue(Action.NAME, getNextString()); + } + if (_nextButton.isVisible()) { + getRootPane().setDefaultButton(_nextButton); + // workaround wrong default button (e.g. on OverviewPage) + if (_activePage.getFocusField() == null) { + _nextButton.grabFocus(); + } + } + _activePage.doActivate(); + } + + private void tryFinish() { + if (finish()) { + this.setVisible(false); + fireFinishedEvent(); + } + } + + private void tryNext() { + if (_activePage == null) + return; + _activePage.validateInput(); + } + + /** + * unlock the wizard dialog, allowing user input + */ + public final void unlock() { + _glassPane.setVisible(false); + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/AbstractWizardHeader.java b/installer/src/java/org/python/util/install/AbstractWizardHeader.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizardHeader.java @@ -0,0 +1,12 @@ +package org.python.util.install; + +import javax.swing.ImageIcon; +import javax.swing.JPanel; + +abstract class AbstractWizardHeader extends JPanel { + protected abstract void setTitle(String title); + + protected abstract void setDescription(String description); + + protected abstract void setIcon(ImageIcon icon); +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/AbstractWizardPage.java b/installer/src/java/org/python/util/install/AbstractWizardPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizardPage.java @@ -0,0 +1,153 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.Insets; +import java.net.URL; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JPanel; + +public abstract class AbstractWizardPage extends JPanel implements TextKeys { + private static final long serialVersionUID = -5233805023557214279L; + + private static final String _ICON_FILE_NAME = "jython_small_c.png"; + + private static ImageIcon _imageIcon = null; + private AbstractWizardValidator _validator = null; + private AbstractWizard _wizard; + + public AbstractWizardPage() { + super(); + setValidator(null); + } + + /** + * This method is called when the wizard page is activated, after Wizard.next(); + */ + protected abstract void activate(); + + /** + * This method is called right before validation of the page + */ + protected abstract void beforeValidate(); + + /** + * called from Wizard, right after this page is set visible + */ + final void doActivate() { + if (getFocusField() != null) + this.getFocusField().grabFocus(); + activate(); + } + + /** + * @return the description of this page, which will be displayed in the wizard header + */ + protected abstract String getDescription(); + + /** + * @return the input field on the page that should grab the focus when the page is activated + */ + protected abstract JComponent getFocusField(); + + /** + * @return the icon that should be displayed in the header in all steps + */ + protected ImageIcon getIcon() { + if (_imageIcon == null) { + URL iconURL = FileHelper.getRelativeURL(getClass(), _ICON_FILE_NAME); + if (iconURL != null) { + _imageIcon = new ImageIcon(iconURL); + } + } + return _imageIcon; + } + + /** + * @return the title of this page, which will be displayed in the wizard header + */ + protected abstract String getTitle(); + + /** + * @return the wizard this page belongs to + */ + public final AbstractWizard getWizard() { + return _wizard; + } + + /** + * @return whether the cancel button is visible + */ + protected abstract boolean isCancelVisible(); + + /** + * @return whether the next button is visible + */ + protected abstract boolean isNextVisible(); + + /** + * @return whether the previous button is visible + */ + protected abstract boolean isPreviousVisible(); + + /** + * this method is called right before the page is hidden, but after the validation + */ + protected abstract void passivate(); + + /** + * Set the validator for this page. The validator is called when the next button is clicked. + * + * @param validator the validator for this page. If this is null, a EmptyValidator is assigned + */ + public final void setValidator(AbstractWizardValidator validator) { + if (validator == null) + this._validator = new EmptyValidator(); + else + this._validator = validator; + this._validator.setWizardPage(this); + this._validator.addValidationListener(_wizard); + } + + /** + * @param wizard the wizard this page belongs to + */ + final void setWizard(AbstractWizard wizard) { + this._wizard = wizard; + _validator.addValidationListener(wizard); + } + + /** + * perform the validation of this page, using the assigned WizardValidator + */ + final void validateInput() { + beforeValidate(); + if (_validator == null) + return; + _validator.start(); + } + + /** + * @return default grid bag constraints + */ + protected GridBagConstraints newGridBagConstraints() { + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.anchor = GridBagConstraints.WEST; + gridBagConstraints.insets = new Insets(5, 5, 5, 5); + return gridBagConstraints; + } + + final String getText(String textKey) { + return Installation.getText(textKey); + } + + final String getText(String textKey, String parameter0) { + return Installation.getText(textKey, parameter0); + } + + final String getText(String textKey, String parameter0, String parameter1) { + return Installation.getText(textKey, parameter0, parameter1); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/AbstractWizardValidator.java b/installer/src/java/org/python/util/install/AbstractWizardValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/AbstractWizardValidator.java @@ -0,0 +1,132 @@ +package org.python.util.install; + +import java.util.ArrayList; +import java.util.Iterator; + +public abstract class AbstractWizardValidator implements TextKeys { + + /** + * The thread that performs the validation + */ + private class ValidatorThread extends Thread { + public final void run() { + try { + fireValidationStarted(); + validate(); + fireValidationSucceeded(); + } catch (ValidationException e) { + fireValidationFailed(e); + } catch (ValidationInformationException vie) { + fireValidationInformationRequired(vie); + } + } + } + + private ArrayList _listeners; + private AbstractWizardPage _page; + private Thread _validatorThread; + + public final void addValidationListener(ValidationListener listener) { + if (listener == null) + return; + if (_listeners == null) + _listeners = new ArrayList(5); + if (_listeners.contains(listener)) + return; + _listeners.add(listener); + } + + private void fireValidationFailed(ValidationException exception) { + if (getWizard() != null) + getWizard().unlock(); + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationFailed(event, exception); + } + } + + private void fireValidationInformationRequired(ValidationInformationException exception) { + if (getWizard() != null) + getWizard().unlock(); + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationInformationRequired(event, exception); + } + } + + private void fireValidationStarted() { + if (getWizard() != null) + getWizard().lock(); + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationStarted(event); + } + } + + private void fireValidationSucceeded() { + if (getWizard() != null) { + getWizard().unlock(); + getWizard().gotoNextPage(); + } + if (_listeners == null || _listeners.isEmpty()) + return; + ValidationEvent event = new ValidationEvent(_page); + for (Iterator it = _listeners.iterator(); it.hasNext();) { + ValidationListener listener = (ValidationListener) it.next(); + listener.validationSucceeded(event); + } + } + + protected final AbstractWizard getWizard() { + if (_page != null) { + return _page.getWizard(); + } else { + return null; + } + } + + protected final AbstractWizardPage getWizardPage() { + return _page; + } + + public final void removeValidationListener(ValidationListener listener) { + if (listener == null || _listeners == null || !_listeners.contains(listener)) + return; + _listeners.remove(listener); + } + + public final void setWizardPage(AbstractWizardPage page) { + this._page = page; + } + + public final void start() { + _validatorThread = new ValidatorThread(); + _validatorThread.start(); + } + + /** + * perform the actual validation + * + * @throws ValidationException when the validation failed + * @throws ValidationInformationException when an information should be displayed + */ + protected abstract void validate() throws ValidationException, ValidationInformationException; + + final String getText(String textKey) { + return Installation.getText(textKey); + } + + final String getText(String textKey, String parameter0) { + return Installation.getText(textKey, parameter0); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ChildProcess.java b/installer/src/java/org/python/util/install/ChildProcess.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ChildProcess.java @@ -0,0 +1,357 @@ +package org.python.util.install; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Easy start of a child process. + *

+ * Features are: + *

    + *
  • wait for the child process to finish. + *
  • kill the child process after a specified timeout. + *
  • get the output of the child process (System.out and System.err) redirected to the calling + * process, unless in silent mode. + *
+ */ +public class ChildProcess { + + /** + * Inner class for reading stdout of the child process and printing it onto the caller's stdout. + */ + private class StdoutMonitor extends Thread { + + private StdoutMonitor() {} + + public void run() { + String line = null; + BufferedReader stdout = new BufferedReader(new InputStreamReader(_process.getInputStream())); + try { + // blocks until input found or process dead + while ((line = stdout.readLine()) != null) { + if (!isSilent()) { + System.out.println(line); + } + } + } catch (IOException ioe) { + if (!isSilent()) { + ioe.printStackTrace(); + } + } finally { + if (stdout != null) + try { + stdout.close(); + } catch (IOException e) {} + } + } + } + + /** + * Inner class for reading stderr of the child process and printing it onto the caller's stderr. + */ + private class StderrMonitor extends Thread { + + private StderrMonitor() {} + + public void run() { + String line = null; + BufferedReader stderr = new BufferedReader(new InputStreamReader(_process.getErrorStream())); + try { + // blocks until input found or process dead + while ((line = stderr.readLine()) != null) { + if (!isSilent()) { + System.err.println(line); + } + } + } catch (IOException ioe) { + if (!isSilent()) { + ioe.printStackTrace(); + } + } finally { + if (stderr != null) + try { + stderr.close(); + } catch (IOException e) {} + } + } + } + + /** + * Constant indicating no timeout at all. + */ + public static final long INFINITE_TIMEOUT = -1; + + /** + * Constant indicating the exit value if the child process was destroyed due to a timeout. + */ + public static final int DESTROYED_AFTER_TIMEOUT = -9898; + + /** + * Constant indicating that the exit value was not yet set + */ + private static final int NOT_SET_EXITVALUE = -9; + + /** + * The command as an array of strings + */ + private String _command[] = null; + + /** + * The timeout (in milliseconds) + */ + private long _timeout = INFINITE_TIMEOUT; + + /** + * The interval for checking if the child process is still alive + */ + private long _pollAliveInterval = 1000; + + /** + * the effective child process + */ + Process _process; + + /** + * the exit value + */ + private int _exitValue = NOT_SET_EXITVALUE; + + /** + * The start time of the child process + */ + private long _startTime; + + /** + * debug option (default is false) + */ + private boolean _debug = false; + + /** + * silent flag + */ + private boolean _silent = false; + + /** + * Default constructor + */ + public ChildProcess() {} + + /** + * Constructor taking a command array as an argument + * + * @param command + * The command to be executed, every token as array element. + */ + public ChildProcess(String command[]) { + setCommand(command); + } + + /** + * Constructor taking a command array and the timeout as an argument + * + * @param command + * The command to be executed, every token as array element. + * @param timeout + * in milliseconds. Special value: INFINITE_TIMEOUT indicates no timeout + * at all. + */ + public ChildProcess(String command[], long timeout) { + setCommand(command); + setTimeout(timeout); + } + + /** + * Set the command array. This will override (but not overwrite) a previously set command + */ + public void setCommand(String command[]) { + _command = command; + } + + /** + * Returns the command array + */ + public String[] getCommand() { + return _command; + } + + /** + * Set the timeout (how long should the calling process wait for the child). + * + * @param timeout + * in milliseconds. Special value: INFINITE_TIMEOUT indicates no timeout + * at all. This is the default. + */ + public void setTimeout(long timeout) { + _timeout = timeout; + } + + /** + * Returns the timeout in milliseconds. + */ + public long getTimeout() { + return _timeout; + } + + /** + * Set the debug flag. + *

+ * Setting this to true will print the submitted command and an information if the child process + * is destroyed after the timeout. + */ + public void setDebug(boolean debug) { + _debug = debug; + } + + /** + * Returns the debug flag + */ + public boolean isDebug() { + return _debug; + } + + /** + * Set the silent flag. + *

+ * Setting this to true will suppress output of the called command. + */ + public void setSilent(boolean silent) { + _silent = silent; + } + + /** + * Returns the silent flag. + */ + public boolean isSilent() { + return _silent; + } + + /** + * Set the interval (in milliseconds) after which the subprocess is checked if it is still + * alive. Defaults to 1000 ms. + */ + public void setPollAliveInterval(long pollAliveInterval) { + _pollAliveInterval = pollAliveInterval; + } + + /** + * Returns the interval (in milliseconds) after which the subprocess is checked if it is still + * alive. + */ + public long getPollAliveInterval() { + return _pollAliveInterval; + } + + /** + * returns true if the timeout has expired + */ + private boolean isTimeout() { + boolean isTimeout = false; + long currentTime = System.currentTimeMillis(); + long diff = 0; + long timeout = getTimeout(); + if (timeout != INFINITE_TIMEOUT) { + diff = currentTime - _startTime; + if (diff > timeout) { + isTimeout = true; + } + } + return isTimeout; + } + + /** + * Start the child process + */ + public int run() { + try { + // determine start time + _startTime = System.currentTimeMillis(); + // start the process + _process = Runtime.getRuntime().exec(getCommand()); + debugCommand(); + // handle stdout and stderr + StdoutMonitor stdoutMonitor = new StdoutMonitor(); + stdoutMonitor.start(); + StderrMonitor stderrMonitor = new StderrMonitor(); + stderrMonitor.start(); + // run the subprocess as long as wanted + while (!isTimeout() && isAlive()) { + try { + Thread.sleep(getPollAliveInterval()); + } catch (InterruptedException ie) { + if (!isSilent()) { + ie.printStackTrace(); + } + } + } + // end properly + if (isAlive()) { // sets the exit value in case process is dead + destroy(); + } else { + if (isDebug()) { + System.out.println("[ChildProcess] ended itself"); + } + } + } catch (IOException ioe) { + if (!isSilent()) { + ioe.printStackTrace(); + } + } + return getExitValue(); + } + + /** + * The exit value + */ + public int getExitValue() { + return _exitValue; + } + + private void setExitValue(int exitValue) { + _exitValue = exitValue; + } + + /** + * Tests if the process is still alive + */ + private boolean isAlive() { + try { + setExitValue(_process.exitValue()); + return false; + } catch (IllegalThreadStateException itse) { + return true; + } + } + + /** + * Destroy the child process + */ + private void destroy() { + _process.destroy(); + setExitValue(DESTROYED_AFTER_TIMEOUT); + if (isDebug()) { + System.out.println("[ChildProcess] destroying because of timeout !"); + } + } + + /** + * Lists the submitted command (if so indicated) + */ + private void debugCommand() { + if (isDebug()) { + String[] command = getCommand(); + if (command != null) { + System.out.print("[ChildProcess] command '"); + for (int i = 0; i < command.length; i++) { + String commandPart = command[i]; + if (i == 0) { + System.out.print(commandPart); + } else { + System.out.print(" " + commandPart); + } + } + System.out.println("' is now running..."); + } + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ConsoleInstaller.java b/installer/src/java/org/python/util/install/ConsoleInstaller.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ConsoleInstaller.java @@ -0,0 +1,609 @@ +package org.python.util.install; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.python.util.install.Installation.JavaVersionInfo; +import org.python.util.install.driver.Tunnel; + +public class ConsoleInstaller implements ProgressListener, TextKeys { + + public static final String CURRENT_JRE = "="; + + private static final String _CANCEL = "c"; + + private static final String _PROMPT = ">>>"; + + private static final String _BEGIN_ANSWERS = "["; + + private static final String _END_ANSWERS = "]"; + + + private InstallerCommandLine _commandLine; + + private JarInstaller _jarInstaller; + + private JarInfo _jarInfo; + + private Tunnel _tunnel; + + public ConsoleInstaller(InstallerCommandLine commandLine, JarInfo jarInfo) { + _commandLine = commandLine; + _jarInfo = jarInfo; + _jarInstaller = new JarInstaller(this, jarInfo); + } + + public void setTunnel(Tunnel tunnel) { + _tunnel = tunnel; + } + + public void install() { + File targetDirectory = null; + JavaHomeHandler javaHomeHandler = null; + if (_commandLine.hasConsoleOption()) { + welcome(); + selectLanguage(); + acceptLicense(); + InstallationType installationType = selectInstallationType(); + targetDirectory = determineTargetDirectory(); + javaHomeHandler = checkVersion(determineJavaHome()); + promptForCopying(targetDirectory, installationType, javaHomeHandler); + _jarInstaller.inflate(targetDirectory, installationType, javaHomeHandler); + showReadme(targetDirectory); + success(targetDirectory); + } else if (_commandLine.hasSilentOption()) { + message(getText(C_SILENT_INSTALLATION)); + targetDirectory = _commandLine.getTargetDirectory(); + checkTargetDirectorySilent(targetDirectory); + javaHomeHandler = checkVersionSilent(_commandLine.getJavaHomeHandler()); + _jarInstaller.inflate(targetDirectory, + _commandLine.getInstallationType(), + javaHomeHandler); + success(targetDirectory); + } + } + + protected final static void message(String message) { + System.out.println(message); // this System.out.println is intended + } + + private void welcome() { + message(getText(C_WELCOME_TO_JYTHON)); + message(getText(C_VERSION_INFO, _jarInfo.getVersion())); + message(getText(C_AT_ANY_TIME_CANCEL, _CANCEL)); + } + + private String question(String question) { + return question(question, null, false, null); + } + + private String question(String question, boolean answerRequired) { + return question(question, null, answerRequired, null); + } + + private String question(String question, List answers, String defaultAnswer) { + return question(question, answers, true, defaultAnswer); + } + + /** + * question and answer + * + * @param question + * @param answers + * Possible answers (may be null) + * @param answerRequired + * @param defaultAnswer + * (may be null) + * + * @return (chosen) answer + */ + private String question(String question, + List answers, + boolean answerRequired, + String defaultAnswer) { + try { + if (answers != null && answers.size() > 0) { + question = question + " " + _BEGIN_ANSWERS; + Iterator answersAsIterator = answers.iterator(); + while (answersAsIterator.hasNext()) { + if (!question.endsWith(_BEGIN_ANSWERS)) + question = question + "/"; + String possibleAnswer = answersAsIterator.next(); + if (possibleAnswer.equalsIgnoreCase(defaultAnswer)) { + if (Character.isDigit(possibleAnswer.charAt(0))) { + question = question.concat(" ").concat(possibleAnswer).concat(" "); + } else { + question = question + possibleAnswer.toUpperCase(); + } + } else { + question = question + possibleAnswer; + } + } + question = question + _END_ANSWERS; + } + question = question + " " + _PROMPT + " "; + boolean match = false; + String answer = ""; + while (!match && !_CANCEL.equalsIgnoreCase(answer)) { + // output to normal System.out + System.out.print(question); // intended print, not println (!) + answer = readLine(); + if ("".equals(answer) && answerRequired) { + // check default answer + if (defaultAnswer != null) { + match = true; + answer = defaultAnswer; + } + } else { + if (answers != null && answers.size() > 0) { + Iterator answersAsIterator = answers.iterator(); + while (answersAsIterator.hasNext()) { + if (answer.equalsIgnoreCase(answersAsIterator.next())) { + match = true; + } + } + } else { + match = true; + } + if (!match && !_CANCEL.equalsIgnoreCase(answer)) { + message(getText(C_INVALID_ANSWER, answer)); + } + } + } + if (_CANCEL.equalsIgnoreCase(answer)) { + throw new InstallationCancelledException(); + } + return answer; + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + + /** + * Send a signal through the tunnel, and then wait for the answer from the other side. + * + *

+     *            (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+     *            (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+     * 
+ */ + private String readLine() throws IOException { + InputStream inputStream; + String line = ""; + if (_tunnel == null) { + inputStream = System.in; + } else { + inputStream = _tunnel.getAnswerReceiverStream(); + _tunnel.getQuestionSenderStream().write(Tunnel.NEW_LINE.getBytes()); + _tunnel.getQuestionSenderStream().flush(); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + line = reader.readLine(); + return line; + } + + private void selectLanguage() { + List availableLanguages = new ArrayList(2); + availableLanguages.add(getText(C_ENGLISH)); + availableLanguages.add(getText(C_GERMAN)); // 1 == German + List answers = new ArrayList(availableLanguages.size()); + String languages = ""; + String defaultAnswer = null; + for (Iterator iterator = availableLanguages.iterator(); iterator.hasNext();) { + String language = iterator.next(); + String possibleAnswer = language.substring(0, 1); + if (defaultAnswer == null) { + defaultAnswer = possibleAnswer; + } + languages = languages + language + ", "; + answers.add(possibleAnswer.toLowerCase()); + } + languages = languages.substring(0, languages.length() - 2); + message(getText(C_AVAILABLE_LANGUAGES, languages)); + String answer = question(getText(C_SELECT_LANGUAGE), answers, defaultAnswer); + if (answer.equalsIgnoreCase(answers.get(1))) { + Installation.setLanguage(Locale.GERMAN); + } else { + Installation.setLanguage(Locale.ENGLISH); + } + } + + private InstallationType selectInstallationType() { + InstallationType installationType = new InstallationType(); + String no = getText(C_NO); + String yes = getText(C_YES); + message(getText(C_INSTALL_TYPES)); + message(" " + Installation.ALL + ". " + getText(C_ALL)); + message(" " + Installation.STANDARD + ". " + getText(C_STANDARD)); + message(" " + Installation.MINIMUM + ". " + getText(C_MINIMUM)); + message(" " + Installation.STANDALONE + ". " + getText(C_STANDALONE)); + String answer = question(getText(C_SELECT_INSTALL_TYPE), getTypeAnswers(), Installation.ALL); + if (Installation.ALL.equals(answer)) { + installationType.setAll(); + } else if (Installation.STANDARD.equals(answer)) { + installationType.setStandard(); + } else if (Installation.MINIMUM.equals(answer)) { + installationType.setMinimum(); + } else if (Installation.STANDALONE.equals(answer)) { + installationType.setStandalone(); + } + if (!installationType.isStandalone()) { + // include parts ? + if (!installationType.isAll()) { + answer = question(getText(C_INCLUDE), getYNAnswers(), no); + if (yes.equals(answer)) { + do { + answer = question(getText(C_INEXCLUDE_PARTS, no), getInExcludeAnswers(), no); + if (InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES.equals(answer)) { + installationType.addLibraryModules(); + } else if (InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES.equals(answer)) { + installationType.addDemosAndExamples(); + } else if (InstallerCommandLine.INEXCLUDE_DOCUMENTATION.equals(answer)) { + installationType.addDocumentation(); + } else if (InstallerCommandLine.INEXCLUDE_SOURCES.equals(answer)) { + installationType.addSources(); + } + if (!no.equals(answer)) { + message(getText(C_SCHEDULED, answer)); + } + } while (!no.equals(answer)); + } + } + // exclude parts ? + if (!installationType.isMinimum()) { + answer = question(getText(C_EXCLUDE), getYNAnswers(), no); + if (yes.equals(answer)) { + do { + answer = question(getText(C_INEXCLUDE_PARTS, no), getInExcludeAnswers(), no); + if (InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES.equals(answer)) { + installationType.removeLibraryModules(); + } else if (InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES.equals(answer)) { + installationType.removeDemosAndExamples(); + } else if (InstallerCommandLine.INEXCLUDE_DOCUMENTATION.equals(answer)) { + installationType.removeDocumentation(); + } else if (InstallerCommandLine.INEXCLUDE_SOURCES.equals(answer)) { + installationType.removeSources(); + } + if (!no.equals(answer)) { + message(getText(C_UNSCHEDULED, answer)); + } + } while (!no.equals(answer)); + } + } + } + return installationType; + } + + private JavaHomeHandler checkVersion(JavaHomeHandler javaHomeHandler) { + // handle target java version + JavaInfo javaInfo = verifyTargetJava(javaHomeHandler); + message(getText(C_JAVA_VERSION, + javaInfo.getJavaVersionInfo().getVendor(), + javaInfo.getJavaVersionInfo().getVersion())); + if (!Installation.isValidJava(javaInfo.getJavaVersionInfo())) { + message(getText(C_UNSUPPORTED_JAVA)); + question(getText(C_PROCEED_ANYWAY)); + } + // handle OS + String osName = System.getProperty(Installation.OS_NAME); + String osVersion = System.getProperty(Installation.OS_VERSION); + message(getText(C_OS_VERSION, osName, osVersion)); + if (!Installation.isValidOs()) { + message(getText(C_UNSUPPORTED_OS)); + question(getText(C_PROCEED_ANYWAY)); + } + return javaInfo.getJavaHomeHandler(); + } + + private JavaHomeHandler checkVersionSilent(JavaHomeHandler javaHomeHandler) { + // check target java version + JavaInfo javaInfo = verifyTargetJava(javaHomeHandler); + if (!Installation.isValidJava(javaInfo.getJavaVersionInfo())) { + message(getText(C_UNSUPPORTED_JAVA)); + } + // check OS + if (!Installation.isValidOs()) { + message(getText(C_UNSUPPORTED_OS)); + } + return javaInfo.getJavaHomeHandler(); + } + + private JavaInfo verifyTargetJava(JavaHomeHandler javaHomeHandler) { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + Installation.fillJavaVersionInfo(javaVersionInfo, System.getProperties()); // a priori + if (javaHomeHandler.isDeviation()) { + javaVersionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + if (javaVersionInfo.getErrorCode() != Installation.NORMAL_RETURN) { + // switch back to current if an error occurred + message(getText(C_TO_CURRENT_JAVA, javaVersionInfo.getReason())); + javaHomeHandler = new JavaHomeHandler(); + } + } + JavaInfo javaInfo = new JavaInfo(); + javaInfo.setJavaHomeHandler(javaHomeHandler); + javaInfo.setJavaVersionInfo(javaVersionInfo); + return javaInfo; + } + + private void acceptLicense() { + String no = getText(C_NO); + String yes = getText(C_YES); + String read = question(getText(C_READ_LICENSE), getYNAnswers(), no); + if (read.equalsIgnoreCase(getText(C_YES))) { + String licenseText = "n/a"; + try { + licenseText = _jarInfo.getLicenseText(); + message(licenseText); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + String accept = question(getText(C_ACCEPT), getYNAnswers(), yes); + if (!accept.equalsIgnoreCase(getText(C_YES))) { + throw new InstallationCancelledException(); + } + } + + private File determineTargetDirectory() { + String no = getText(C_NO); + String yes = getText(C_YES); + File targetDirectory = null; + try { + do { + targetDirectory = new File(question(getText(C_ENTER_TARGET_DIRECTORY), true)); + if (targetDirectory.exists()) { + if (!targetDirectory.isDirectory()) { + message(getText(C_NOT_A_DIRECTORY, targetDirectory.getCanonicalPath())); + } else { + if (targetDirectory.list().length > 0) { + String overwrite = question(getText(C_OVERWRITE_DIRECTORY, + targetDirectory.getCanonicalPath()), + getYNAnswers(), + no); + if (overwrite.equalsIgnoreCase(getText(C_YES))) { + String clear = question(getText(C_CLEAR_DIRECTORY, + targetDirectory.getCanonicalPath()), + getYNAnswers(), + yes); + if (clear.equalsIgnoreCase(getText(C_YES))) { + clearDirectory(targetDirectory); + } + } + } + } + } else { + String create = question(getText(C_CREATE_DIRECTORY, + targetDirectory.getCanonicalPath()), + getYNAnswers(), + yes); + if (create.equalsIgnoreCase(getText(C_YES))) { + if (!targetDirectory.mkdirs()) { + throw new InstallerException(getText(C_UNABLE_CREATE_DIRECTORY, + targetDirectory.getCanonicalPath())); + } + } + } + } while (!targetDirectory.exists() || !targetDirectory.isDirectory() + || targetDirectory.list().length > 0); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + return targetDirectory; + } + + private JavaHomeHandler determineJavaHome() { + JavaHomeHandler javaHomeHandler = null; + boolean javaFound = false; + while (!javaFound) { + String javaHomeName = question(getText(C_ENTER_JAVA_HOME), null, true, CURRENT_JRE); + // only validate deviations + if (CURRENT_JRE.equals(javaHomeName)) { + javaHomeHandler = new JavaHomeHandler(); + javaFound = true; + } else { + javaHomeHandler = new JavaHomeHandler(javaHomeName); + if (javaHomeHandler.isDeviation()) { + if (!javaHomeHandler.isValidHome()) { + String binDirName = javaHomeName.concat("/bin"); + message(getText(C_NO_JAVA_EXECUTABLE, binDirName)); + } else { + javaFound = true; + } + } else { + javaFound = true; + } + } + } + return javaHomeHandler; + } + + private void checkTargetDirectorySilent(File targetDirectory) { + try { + if (!targetDirectory.exists()) { + // create directory + if (!targetDirectory.mkdirs()) { + throw new InstallerException(getText(C_UNABLE_CREATE_DIRECTORY, + targetDirectory.getCanonicalPath())); + } + } else { + // assert it is an empty directory + if (!targetDirectory.isDirectory()) { + throw new InstallerException(getText(C_NOT_A_DIRECTORY, + targetDirectory.getCanonicalPath())); + } else { + if (targetDirectory.list().length > 0) { + throw new InstallerException(getText(C_NON_EMPTY_TARGET_DIRECTORY, + targetDirectory.getCanonicalPath())); + } + } + } + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + + private void showReadme(final File targetDirectory) { + String no = getText(C_NO); + String read = question(getText(C_READ_README), getYNAnswers(), no); + if (read.equalsIgnoreCase(getText(C_YES))) { + try { + message(_jarInfo.getReadmeText()); + question(getText(C_PROCEED)); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + } + + private void clearDirectory(File targetDirectory) { + File files[] = targetDirectory.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + clearDirectory(files[i]); + } + if (!files[i].delete()) { + throw new InstallerException(getText(C_UNABLE_TO_DELETE, files[i].getAbsolutePath())); + } + } + } + + private void promptForCopying(final File targetDirectory, + final InstallationType installationType, + final JavaHomeHandler javaHomeHandler) { + try { + message(getText(C_SUMMARY)); + if (installationType.isStandalone()) { + message(" - " + InstallerCommandLine.TYPE_STANDALONE); + } else { + message(" - " + InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES + ": " + + installationType.installLibraryModules()); + message(" - " + InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES + ": " + + installationType.installDemosAndExamples()); + message(" - " + InstallerCommandLine.INEXCLUDE_DOCUMENTATION + ": " + + installationType.installDocumentation()); + message(" - " + InstallerCommandLine.INEXCLUDE_SOURCES + ": " + + installationType.installSources()); + if (javaHomeHandler.isValidHome()) { + message(" - JRE: " + javaHomeHandler.getHome().getAbsolutePath()); + } else { + message(" - java"); + } + } + String proceed = question(getText(C_CONFIRM_TARGET, targetDirectory.getCanonicalPath()), + getYNAnswers(), getText(C_YES)); + if (!proceed.equalsIgnoreCase(getText(C_YES))) { + throw new InstallationCancelledException(); + } + } catch (IOException ioe) { + throw new InstallerException(ioe); // catch for the compiler + } + } + + private void success(final File targetDirectory) { + try { + message(getText(C_CONGRATULATIONS) + " " + + getText(C_SUCCESS, _jarInfo.getVersion(), targetDirectory.getCanonicalPath())); + } catch (IOException ioe) { + throw new InstallerException(ioe); // catch for the compiler + } + } + + private List getTypeAnswers() { + List answers = new ArrayList(4); + answers.add(Installation.ALL); + answers.add(Installation.STANDARD); + answers.add(Installation.MINIMUM); + answers.add(Installation.STANDALONE); + return answers; + } + + private List getYNAnswers() { + List answers = new ArrayList(2); + answers.add(getText(C_YES)); + answers.add(getText(C_NO)); + return answers; + } + + private List getInExcludeAnswers() { + List answers = new ArrayList(5); + answers.add(InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES); + answers.add(InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES); + answers.add(InstallerCommandLine.INEXCLUDE_DOCUMENTATION); + answers.add(InstallerCommandLine.INEXCLUDE_SOURCES); + answers.add(getText(C_NO)); + return answers; + } + + private void progressMessage(int percentage) { + message(" " + percentage + " %"); + } + + private String getText(String textKey) { + return Installation.getText(textKey); + } + + private String getText(String textKey, String parameter0) { + return Installation.getText(textKey, parameter0); + } + + private String getText(String textKey, String parameter0, String parameter1) { + return Installation.getText(textKey, parameter0, parameter1); + } + + private static class JavaInfo { + + private JavaVersionInfo _javaVersionInfo; + + private JavaHomeHandler _javaHomeHandler; + + void setJavaHomeHandler(JavaHomeHandler javaHomeHandler) { + _javaHomeHandler = javaHomeHandler; + } + + JavaHomeHandler getJavaHomeHandler() { + return _javaHomeHandler; + } + + void setJavaVersionInfo(JavaVersionInfo javaVersionInfo) { + _javaVersionInfo = javaVersionInfo; + } + + JavaVersionInfo getJavaVersionInfo() { + return _javaVersionInfo; + } + } + + // + // interface ProgressListener + // + public void progressChanged(int newPercentage) { + progressMessage(newPercentage); + } + + public int getInterval() { + return 10; // fixed interval for console installer + } + + public void progressFinished() { + progressMessage(100); + } + + public void progressEntry(String entry) { + // ignore the single entries - only used in gui mode + } + + public void progressStartScripts() { + message(getText(C_GENERATING_START_SCRIPTS)); + } + + public void progressStandalone() { + message(getText(C_PACKING_STANDALONE_JAR)); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/DirectoryFilter.java b/installer/src/java/org/python/util/install/DirectoryFilter.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/DirectoryFilter.java @@ -0,0 +1,23 @@ +package org.python.util.install; + +import java.io.File; + +import javax.swing.filechooser.FileFilter; + +/** + * Filters all directories, and sets the description in the file chooser + */ +public class DirectoryFilter extends FileFilter { + + public boolean accept(File f) { + if (f.isDirectory()) { + return true; + } else { + return false; + } + } + + public String getDescription() { + return Installation.getText(TextKeys.DIRECTORIES_ONLY); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/DirectorySelectionPage.java b/installer/src/java/org/python/util/install/DirectorySelectionPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/DirectorySelectionPage.java @@ -0,0 +1,200 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.File; +import java.io.IOException; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +public class DirectorySelectionPage extends AbstractWizardPage { + + private static final long serialVersionUID = -3672273150338356549L; + + private JLabel _label; + private JTextField _directory; + private JButton _browse; + private JarInfo _jarInfo; + + public DirectorySelectionPage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + initComponents(); + } + + private void initComponents() { + // label + _label = new JLabel(); + // directory + _directory = new JTextField(40); + _directory.addFocusListener(new DirectoryFocusListener()); + // browse button + _browse = new JButton(); + _browse.addActionListener(new BrowseButtonListener()); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panel.add(_label, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(_directory, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panel.add(_browse, gridBagConstraints); + + add(panel); + } + + JTextField getDirectory() { + return _directory; + } + + protected String getTitle() { + return getText(TARGET_DIRECTORY_PROPERTY); + } + + protected String getDescription() { + return getText(CHOOSE_LOCATION); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _directory; + } + + protected void activate() { + _label.setText(getText(SELECT_TARGET_DIRECTORY) + ": "); + _browse.setText(getText(BROWSE)); + String directory = FrameInstaller.getTargetDirectory(); + if (directory == null || directory.length() <= 0) { + File defaultDirectory = getDefaultDirectory(); + try { + directory = defaultDirectory.getCanonicalPath(); + } catch (IOException e) { + directory = "?"; + } + FrameInstaller.setTargetDirectory(directory); + } + _directory.setText(FrameInstaller.getTargetDirectory()); + _directory.setToolTipText(_directory.getText()); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private File getDefaultDirectory() { + String directory = ""; + File defaultDirectory = null; + // 1st try (on windows): root + if (Installation.isWindows()) { + JavaHomeHandler handler = new JavaHomeHandler(); + if (handler.isValidHome()) { + directory = handler.getHome().getAbsolutePath(); + if (directory.length() > 2) { + directory = directory.substring(0, 2); + } + } else { + directory = "C:"; + } + defaultDirectory = makeJythonSubDirectory(directory); + } + // 2st try: user.home + if (defaultDirectory == null) { + directory = System.getProperty("user.home", ""); + if (directory.length() > 0) { + defaultDirectory = makeJythonSubDirectory(directory); + } + } + // 3rd try: user.dir + if (defaultDirectory == null) { + directory = System.getProperty("user.dir", ""); + if (directory.length() > 0) { + defaultDirectory = makeJythonSubDirectory(directory); + } + } + // 4th try: current directory + if (defaultDirectory == null) { + defaultDirectory = makeJythonSubDirectory(new File(new File("dummy").getAbsolutePath()).getParent()); + } + return defaultDirectory; + } + + private File makeJythonSubDirectory(String directory) { + File defaultDirectory = null; + File parentDirectory = new File(directory); + if (parentDirectory.exists() && parentDirectory.isDirectory()) { + String jythonSubDirectoryName = "jython" + (_jarInfo.getVersion()).replaceAll("\\+", ""); + defaultDirectory = new File(parentDirectory, jythonSubDirectoryName); + } + return defaultDirectory; + } + + private class BrowseButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String directoryName = _directory.getText(); + File directory = new File(directoryName); + if (directory.exists()) { + if (!directory.isDirectory()) { + // switch to parent directory if user typed the name of a file + directory = directory.getParentFile(); + } + } + JFileChooser fileChooser = new JFileChooser(directory); + fileChooser.setDialogTitle(getText(SELECT_TARGET_DIRECTORY)); + // the filter is at the moment only used for the title of the dialog: + fileChooser.setFileFilter(new DirectoryFilter()); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (fileChooser.isAcceptAllFileFilterUsed()) { + if (Installation.isMacintosh() && Installation.isJDK141()) { + // work around ArrayIndexOutOfBoundsExceptio on Mac OS X, java version 1.4.1 + } else { + fileChooser.setAcceptAllFileFilterUsed(false); + } + } + int returnValue = fileChooser.showDialog(_browse, getText(SELECT)); + if (returnValue == JFileChooser.APPROVE_OPTION) { + _directory.setText(fileChooser.getSelectedFile().getAbsolutePath()); + _directory.setToolTipText(_directory.getText()); + FrameInstaller.setTargetDirectory(_directory.getText()); + } + } + } + + private class DirectoryFocusListener implements FocusListener { + public void focusGained(FocusEvent e) { + } + + public void focusLost(FocusEvent e) { + FrameInstaller.setTargetDirectory(_directory.getText()); + _directory.setToolTipText(_directory.getText()); + } + } + +} diff --git a/installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java b/installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/DirectorySelectionPageValidator.java @@ -0,0 +1,36 @@ +package org.python.util.install; + +import java.io.File; + +public class DirectorySelectionPageValidator extends AbstractWizardValidator { + + DirectorySelectionPage _page; + + DirectorySelectionPageValidator(DirectorySelectionPage page) { + super(); + _page = page; + } + + protected void validate() throws ValidationException, ValidationInformationException { + String directory = _page.getDirectory().getText().trim(); // trim to be sure + if (directory != null && directory.length() > 0) { + File targetDirectory = new File(directory); + if (targetDirectory.exists()) { + if (targetDirectory.isDirectory()) { + if (targetDirectory.list().length > 0) { + throw new ValidationException(getText(NON_EMPTY_TARGET_DIRECTORY)); + } + } + } else { + if (targetDirectory.mkdirs()) { + throw new ValidationInformationException(Installation.getText(CREATED_DIRECTORY, directory)); + } else { + throw new ValidationException(getText(UNABLE_CREATE_DIRECTORY, directory)); + } + } + } else { + throw new ValidationException(getText(EMPTY_TARGET_DIRECTORY)); + } + FrameInstaller.setTargetDirectory(directory); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/EmptyValidator.java b/installer/src/java/org/python/util/install/EmptyValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/EmptyValidator.java @@ -0,0 +1,8 @@ +package org.python.util.install; + +public class EmptyValidator extends AbstractWizardValidator { + + protected void validate() { + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/FileHelper.java b/installer/src/java/org/python/util/install/FileHelper.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/FileHelper.java @@ -0,0 +1,208 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * Helper methods for file handling during installation / installation verification + */ +public final class FileHelper { + + private final static String EXECUTABLE_MODE = "755"; + + /** + * create a temporary directory with the same name as the passed in File (which may exist as + * file, not directory) + * + * @param tempDirectory + * @return true only if the the directory was successfully created (or already + * existed) + */ + public static boolean createTempDirectory(File tempDirectory) { + boolean success = true; + if (!tempDirectory.isDirectory()) { + if (tempDirectory.exists()) { + success = carryOnResult(tempDirectory.delete(), success); + } + if (success) { + success = tempDirectory.mkdirs(); + } + } + return success; + } + + /** + * completely remove a directory + * + * @param dir + * @return true if successful, false otherwise. + */ + public static boolean rmdir(File dir) { + boolean success = true; + if (dir.exists()) { + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + File file = files[i]; + if (file.isFile()) { + success = carryOnResult(file.delete(), success); + } else { + if (file.isDirectory()) { + success = carryOnResult(rmdir(file), success); + } + } + } + success = carryOnResult(dir.delete(), success); + } + return success; + } + + /** + * read the contents of a file into a String + * + * @param file + * The file has to exist + * @return The contents of the file as String + * @throws IOException + */ + public static String readAll(File file) throws IOException { + FileReader fileReader = new FileReader(file); + try { + StringBuffer sb = new StringBuffer(); + char[] b = new char[8192]; + int n; + while ((n = fileReader.read(b)) > 0) { + sb.append(b, 0, n); + } + return sb.toString(); + } finally { + fileReader.close(); + } + } + + /** + * read the contents of a stream into a String + *

+ * ATTN: does not handle encodings + * + * @param inputStream + * The input stream + * @return A String representation of the file contents + * @throws IOException + */ + public static String readAll(InputStream inputStream) throws IOException { + try { + StringBuffer sb = new StringBuffer(); + byte[] b = new byte[8192]; + int n; + while ((n = inputStream.read(b)) > 0) { + sb.append(new String(b, 0, n)); + } + return sb.toString(); + } finally { + inputStream.close(); + } + } + + /** + * Write contents to a file. + *

+ * An existing file would be overwritten. + * + * @param file + * @param contents + * + * @throws IOException + */ + public static void write(File file, String contents) throws IOException { + FileWriter writer = new FileWriter(file); + writer.write(contents); + writer.flush(); + writer.close(); + } + + /** + * determine the url of a file relative to (in the same directory as) the specified .class file
+ * can also be used if the .class file resides inside a .jar file + * + * @param clazz + * The class next to the file + * @param fileName + * The name of the file + * + * @return The url of the file, can be null + */ + public static URL getRelativeURL(Class clazz, String fileName) { + String filePath = getRelativePackagePath(clazz) + "/" + fileName; + return Thread.currentThread().getContextClassLoader().getResource(filePath); + } + + /** + * get the input stream of a file relative to (in the same directory as) the specified .class + * file
+ * can also be used if the .class file resides inside a .jar file + * + * @param clazz + * The class next to the file + * @param fileName + * The name of the file + * + * @return The input stream of the file, can be null + */ + public static InputStream getRelativeURLAsStream(Class clazz, String fileName) { + String filePath = getRelativePackagePath(clazz) + "/" + fileName; + return Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath); + } + + /** + * do a chmod on the passed file + * + * @param scriptFile + */ + public static void makeExecutable(File scriptFile) { + try { + String command[] = new String[3]; + command[0] = "chmod"; + command[1] = EXECUTABLE_MODE; + command[2] = scriptFile.getAbsolutePath(); + long timeout = 3000; + ChildProcess childProcess = new ChildProcess(command, timeout); + childProcess.run(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * build the package path for the class loader
+ * the class loader should be able to load a file appended to this path, if it is in the same + * directory. + * + * @param clazz + * The class + * + * @return The package path + */ + private static String getRelativePackagePath(Class clazz) { + String className = clazz.getName(); + String packageName = className.substring(0, className.lastIndexOf(".")); + return packageName.replace('.', '/'); + } + + /** + * @param newResult + * @param existingResult + * @return false if newResult or existingResult are false, true + * otherwise. + */ + private static boolean carryOnResult(boolean newResult, boolean existingResult) { + if (existingResult) { + return newResult; + } else { + return existingResult; + } + } +} diff --git a/installer/src/java/org/python/util/install/FrameInstaller.java b/installer/src/java/org/python/util/install/FrameInstaller.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/FrameInstaller.java @@ -0,0 +1,186 @@ +package org.python.util.install; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.Locale; +import java.util.Properties; + +import javax.swing.UIManager; + +import org.python.util.install.Installation.JavaVersionInfo; +import org.python.util.install.driver.Autotest; + +public class FrameInstaller { + private static final String TRUE = "1"; + private static final String FALSE = "0"; + + private static final String JAVA_VERSION_PROPERTY = "FrameInstaller.Version"; + private static final String JAVA_VENDOR_PROPERTY = "FrameInstaller.Vendor"; + private static final String JAVA_SPEC_VERSION_PROPERTY = "FrameInstaller.SpecVersion"; + + private static final String INEX_MOD_PROPERTY = "FrameInstaller.mod"; + private static final String INEX_DEMO_PROPERTY = "FrameInstaller.demo"; + private static final String INEX_DOC_PROPERTY = "FrameInstaller.doc"; + private static final String INEX_SRC_PROPERTY = "FrameInstaller.src"; + private static final String STANDALONE_PROPERTY = "FrameInstaller.standalone"; + + private static Properties _properties = new Properties(); + + private static JavaHomeHandler _javaHomeHandler; + + protected FrameInstaller(InstallerCommandLine commandLine, JarInfo jarInfo, Autotest autotest) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + } + // clear all properties + _properties.clear(); + // set the default for the target directory + if (commandLine.hasDirectoryOption()) { + setTargetDirectory(commandLine.getTargetDirectory().getAbsolutePath()); + } + if (commandLine.hasJavaHomeOption()) { + setJavaHomeHandler(commandLine.getJavaHomeHandler()); + } + initDefaultJava(); + Wizard wizard = new Wizard(jarInfo, autotest); + wizard.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent event) { + if (!Installation.isAutotesting()) { + System.exit(0); + } + } + }); + wizard.addWizardListener(new SimpleWizardListener()); + wizard.setVisible(true); + } + + protected static void setProperty(String key, String value) { + _properties.setProperty(key, value); + } + + protected static String getProperty(String key) { + return _properties.getProperty(key); + } + + protected static String getProperty(String key, String defaultValue) { + return _properties.getProperty(key, defaultValue); + } + + protected static void setTargetDirectory(String targetDirectory) { + setProperty(TextKeys.TARGET_DIRECTORY_PROPERTY, targetDirectory.trim()); + } + + protected static String getTargetDirectory() { + return getProperty(TextKeys.TARGET_DIRECTORY_PROPERTY); + } + + protected static void setJavaHomeHandler(JavaHomeHandler javaHomeHandler) { + _javaHomeHandler = javaHomeHandler; + } + + protected static JavaHomeHandler getJavaHomeHandler() { + if (_javaHomeHandler == null) { + _javaHomeHandler = new JavaHomeHandler(); + } + return _javaHomeHandler; + } + + protected static void setLanguage(Locale locale) { + setProperty(TextKeys.LANGUAGE_PROPERTY, locale.toString()); + Installation.setLanguage(locale); + } + + protected static Locale getLanguage() { + return new Locale(getProperty(TextKeys.LANGUAGE_PROPERTY)); + } + + protected static InstallationType getInstallationType() { + InstallationType installationType = new InstallationType(); + if (Boolean.valueOf(getProperty(STANDALONE_PROPERTY)).booleanValue()) { + installationType.setStandalone(); + } + if (Boolean.valueOf(getProperty(INEX_MOD_PROPERTY)).booleanValue()) { + installationType.addLibraryModules(); + } else { + installationType.removeLibraryModules(); + } + if (Boolean.valueOf(getProperty(INEX_DEMO_PROPERTY)).booleanValue()) { + installationType.addDemosAndExamples(); + } else { + installationType.removeDemosAndExamples(); + } + if (Boolean.valueOf(getProperty(INEX_DOC_PROPERTY)).booleanValue()) { + installationType.addDocumentation(); + } else { + installationType.removeDocumentation(); + } + if (Boolean.valueOf(getProperty(INEX_SRC_PROPERTY)).booleanValue()) { + installationType.addSources(); + } else { + installationType.removeSources(); + } + return installationType; + } + + protected static void setInstallationType(InstallationType installationType) { + setProperty(STANDALONE_PROPERTY, Boolean.toString(installationType.isStandalone())); + setProperty(INEX_MOD_PROPERTY, Boolean.toString(installationType.installLibraryModules())); + setProperty(INEX_DEMO_PROPERTY, Boolean.toString(installationType.installDemosAndExamples())); + setProperty(INEX_DOC_PROPERTY, Boolean.toString(installationType.installDocumentation())); + setProperty(INEX_SRC_PROPERTY, Boolean.toString(installationType.installSources())); + } + + protected static JavaVersionInfo getJavaVersionInfo() { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + javaVersionInfo.setVersion(getProperty(JAVA_VERSION_PROPERTY)); + javaVersionInfo.setVendor(getProperty(JAVA_VENDOR_PROPERTY)); + javaVersionInfo.setSpecificationVersion(getProperty(JAVA_SPEC_VERSION_PROPERTY)); + return javaVersionInfo; + } + + protected static void setJavaVersionInfo(JavaVersionInfo javaVersionInfo) { + setProperty(JAVA_VERSION_PROPERTY, javaVersionInfo.getVersion()); + setProperty(JAVA_VENDOR_PROPERTY, javaVersionInfo.getVendor()); + setProperty(JAVA_SPEC_VERSION_PROPERTY, javaVersionInfo.getSpecificationVersion()); + } + + protected static void setAccept(boolean accept) { + if (accept) { + setProperty(TextKeys.ACCEPT_PROPERTY, TRUE); + } else { + setProperty(TextKeys.ACCEPT_PROPERTY, FALSE); + } + } + + protected static boolean isAccept() { + return TRUE.equals(getProperty(TextKeys.ACCEPT_PROPERTY, FALSE)); + } + + protected static void initDefaultJava() { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + Installation.fillJavaVersionInfo(javaVersionInfo, System.getProperties()); + FrameInstaller.setJavaVersionInfo(javaVersionInfo); + } + + private class SimpleWizardListener implements WizardListener { + public void wizardStarted(WizardEvent event) { + } + + public void wizardFinished(WizardEvent event) { + if (!Installation.isAutotesting()) { + System.exit(0); + } + } + + public void wizardCancelled(WizardEvent event) { + System.exit(1); + } + + public void wizardNext(WizardEvent event) { + } + + public void wizardPrevious(WizardEvent event) { + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/Installation.java b/installer/src/java/org/python/util/install/Installation.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/Installation.java @@ -0,0 +1,439 @@ +package org.python.util.install; + +import java.awt.GraphicsEnvironment; // should be allowed on headless systems +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.StringTokenizer; + +import org.python.util.install.driver.Autotest; +import org.python.util.install.driver.InstallationDriver; +import org.python.util.install.driver.Tunnel; + +public class Installation { + public final static int NORMAL_RETURN = 0; + public final static int ERROR_RETURN = 1; + + protected static final String ALL = "1"; + protected static final String STANDARD = "2"; + protected static final String MINIMUM = "3"; + protected static final String STANDALONE = "9"; + + protected static final String OS_NAME = "os.name"; + protected static final String OS_VERSION = "os.version"; + protected static final String JAVA_VM_NAME = "java.vm.name"; + protected static final String EMPTY = ""; + + protected static final String HEADLESS_PROPERTY_NAME = "java.awt.headless"; + + private static final String RESOURCE_CLASS = "org.python.util.install.TextConstants"; + + private static ResourceBundle _textConstants = ResourceBundle.getBundle(RESOURCE_CLASS, Locale.getDefault()); + + private static boolean _verbose = false; + private static boolean _isAutotesting = false; + + public static void main(String args[]) { + internalMain(args, null, null); + } + + public static void driverMain(String args[], Autotest autotest, Tunnel tunnel) { + internalMain(args, autotest, tunnel); + } + + protected static boolean isVerbose() { + return _verbose; + } + + protected static void setVerbose(boolean verbose) { + _verbose = verbose; + } + + protected static boolean isAutotesting() { + return _isAutotesting; + } + + protected static String getText(String key) { + return _textConstants.getString(key); + } + + protected static String getText(String key, String... parameters) { + return MessageFormat.format(_textConstants.getString(key), (Object[])parameters); + } + + protected static void setLanguage(Locale locale) { + _textConstants = ResourceBundle.getBundle(RESOURCE_CLASS, locale); + } + + public static boolean isValidOs() { + String osName = System.getProperty(OS_NAME, ""); + String lowerOs = osName.toLowerCase(); + if (isWindows()) { + return true; + } + if (lowerOs.indexOf("linux") >= 0) { + return true; + } + if (lowerOs.indexOf("mac") >= 0) { + return true; + } + if (lowerOs.indexOf("unix") >= 0) { + return true; + } + return false; + } + + protected static boolean isValidJava(JavaVersionInfo javaVersionInfo) { + String specificationVersion = javaVersionInfo.getSpecificationVersion(); + verboseOutput("specification version: '" + specificationVersion + "'"); + boolean valid = true; + if (getJavaSpecificationVersion(specificationVersion) < 15) { + valid = false; + } + return valid; + } + + /** + * @return specification version as an int, e.g. 15 or 16 (the micro part is ignored) + * @param specificationVersion + * as system property + */ + public static int getJavaSpecificationVersion(String specificationVersion) { + // major.minor.micro + // according to http://java.sun.com/j2se/1.5.0/docs/guide/versioning/spec/versioning2.html + String major = "1"; + String minor = "0"; + StringTokenizer tokenizer = new StringTokenizer(specificationVersion, "."); + if (tokenizer.hasMoreTokens()) { + major = tokenizer.nextToken(); + } + if (tokenizer.hasMoreTokens()) { + minor = tokenizer.nextToken(); + } + return Integer.valueOf(major.concat(minor)).intValue(); + } + + public static boolean isWindows() { + boolean isWindows = false; + String osName = System.getProperty(OS_NAME, ""); + if (osName.toLowerCase().indexOf("windows") >= 0) { + isWindows = true; + } + return isWindows; + } + + protected static boolean isMacintosh() { + boolean isMacintosh = false; + String osName = System.getProperty(OS_NAME, ""); + if (osName.toLowerCase().indexOf("mac") >= 0) { + isMacintosh = true; + } + return isMacintosh; + } + + protected static boolean isGNUJava() { + boolean isGNUJava = false; + String javaVmName = System.getProperty(JAVA_VM_NAME, ""); + String lowerVmName = javaVmName.toLowerCase(); + if (lowerVmName.indexOf("gnu") >= 0 && lowerVmName.indexOf("libgcj") >= 0) { + isGNUJava = true; + } + return isGNUJava; + } + + protected static boolean isJDK141() { + boolean isJDK141 = false; + String javaVersion = System.getProperty(JavaVersionTester.JAVA_VERSION, ""); + if (javaVersion.toLowerCase().startsWith("1.4.1")) { + isJDK141 = true; + } + return isJDK141; + } + + /** + * Get the version info of an external (maybe other) jvm. + * + * @param javaHomeHandler + * The java home handler pointing to the java home of the external jvm.
+ * The /bin directory is assumed to be a direct child directory. + * + * @return The versionInfo + */ + protected static JavaVersionInfo getExternalJavaVersion(JavaHomeHandler javaHomeHandler) { + JavaVersionInfo versionInfo = new JavaVersionInfo(); + if (javaHomeHandler.isValidHome()) { + try { + ConsoleInstaller.message(getText(TextKeys.C_CHECK_JAVA_VERSION)); + // launch the java command - temporary file will be written by the child process + File tempFile = File.createTempFile("jython_installation", ".properties"); + if (tempFile.exists() && tempFile.canWrite()) { + String command[] = new String[5]; + command[0] = javaHomeHandler.getExecutableName(); + command[1] = "-cp"; + // our own class path should be ok here + command[2] = System.getProperty("java.class.path"); + command[3] = JavaVersionTester.class.getName(); + command[4] = tempFile.getAbsolutePath(); + verboseOutput("executing: " + command[0] + " " + command[1] + " " + command[2] + + " " + command[3] + " " + command[4]); + ChildProcess childProcess = new ChildProcess(command, 10000); // 10 seconds + childProcess.setDebug(Installation.isVerbose()); + int errorCode = childProcess.run(); + if (errorCode != NORMAL_RETURN) { + versionInfo.setErrorCode(errorCode); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, javaHomeHandler.toString())); + } else { + Properties tempProperties = new Properties(); + tempProperties.load(new FileInputStream(tempFile)); + fillJavaVersionInfo(versionInfo, tempProperties); + } + } else { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_UNABLE_CREATE_TMPFILE, tempFile.getAbsolutePath())); + } + } catch (IOException e) { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, javaHomeHandler.toString())); + } + } else { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, javaHomeHandler.toString())); + } + + return versionInfo; + } + + /** + * @return The system default java version + */ + public static JavaVersionInfo getDefaultJavaVersion() { + JavaVersionInfo versionInfo = new JavaVersionInfo(); + String executableName = "java"; + try { + // launch the java command - temporary file will be written by the child process + File tempFile = File.createTempFile("jython_installation", ".properties"); + if (tempFile.exists() && tempFile.canWrite()) { + String command[] = new String[5]; + command[0] = executableName; + command[1] = "-cp"; + // our own class path should be ok here + command[2] = System.getProperty("java.class.path"); + command[3] = JavaVersionTester.class.getName(); + command[4] = tempFile.getAbsolutePath(); + ChildProcess childProcess = new ChildProcess(command, 10000); // 10 seconds + childProcess.setDebug(false); + int errorCode = childProcess.run(); + if (errorCode != NORMAL_RETURN) { + versionInfo.setErrorCode(errorCode); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, executableName)); + } else { + Properties tempProperties = new Properties(); + tempProperties.load(new FileInputStream(tempFile)); + fillJavaVersionInfo(versionInfo, tempProperties); + } + } else { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_UNABLE_CREATE_TMPFILE, + tempFile.getAbsolutePath())); + } + } catch (IOException e) { + versionInfo.setErrorCode(ERROR_RETURN); + versionInfo.setReason(getText(TextKeys.C_NO_VALID_JAVA, executableName)); + } + return versionInfo; + } + + protected static void fillJavaVersionInfo(JavaVersionInfo versionInfo, Properties properties) { + versionInfo.setVersion(properties.getProperty(JavaVersionTester.JAVA_VERSION)); + versionInfo.setSpecificationVersion(properties.getProperty(JavaVersionTester.JAVA_SPECIFICATION_VERSION)); + versionInfo.setVendor(properties.getProperty(JavaVersionTester.JAVA_VENDOR)); + } + + public static class JavaVersionInfo { + private String _version; + private String _specificationVersion; + private String _vendor; + private int _errorCode; + private String _reason; + + protected JavaVersionInfo() { + _version = EMPTY; + _specificationVersion = EMPTY; + _errorCode = NORMAL_RETURN; + _reason = EMPTY; + } + + protected void setVersion(String version) { + _version = version; + } + + protected void setSpecificationVersion(String specificationVersion) { + _specificationVersion = specificationVersion; + } + + protected void setVendor(String vendor) { + _vendor = vendor; + } + + protected void setErrorCode(int errorCode) { + _errorCode = errorCode; + } + + protected void setReason(String reason) { + _reason = reason; + } + + protected String getVersion() { + return _version; + } + + public String getSpecificationVersion() { + return _specificationVersion; + } + + protected String getVendor() { + return _vendor; + } + + public int getErrorCode() { + return _errorCode; + } + + protected String getReason() { + return _reason; + } + } + + protected static class JavaFilenameFilter implements FilenameFilter { + public boolean accept(File dir, String name) { + if (name.toLowerCase().startsWith("java")) { + return true; + } else { + return false; + } + } + } + + public static boolean isGuiAllowed() { + verboseOutput("checking gui availability"); + if (Boolean.getBoolean(HEADLESS_PROPERTY_NAME)) { + verboseOutput(HEADLESS_PROPERTY_NAME + " is true"); + return false; + } else if (GraphicsEnvironment.isHeadless()) { + verboseOutput("GraphicsEnvironment is headless"); + return false; + } else { + try { + verboseOutput("trying to get the GraphicsEnvironment"); + GraphicsEnvironment.getLocalGraphicsEnvironment(); + verboseOutput("got the GraphicsEnvironment!"); + return true; + } catch (Throwable t) { + verboseOutput("got the following exception:"); + verboseOutput(t); + return false; + } + } + } + + // + // private methods + // + + private static boolean useGui(InstallerCommandLine commandLine) { + if (commandLine.hasConsoleOption() || commandLine.hasSilentOption()) { + return false; + } + return isGuiAllowed(); + } + + /** + * In the normal case, this method is called with (args, null, null), see main(args). + *

+ * However, in autotesting mode (commandLine.hasAutotestOption()), we pass in Autotest and Tunnel, + * see driverMain(args, autotest, tunnel). + *

+ * This means that in autotesting mode this method will call itself (via InstallationDriver.drive()), + * but with different arguments. + */ + private static void internalMain(String[] args, Autotest autotest, Tunnel tunnel) { + try { + setVerbose(InstallerCommandLine.hasVerboseOptionInArgs(args)); + dumpSystemProperties(); + verboseOutput("reading jar info"); + JarInfo jarInfo = new JarInfo(); + InstallerCommandLine commandLine = new InstallerCommandLine(jarInfo); + if (!commandLine.setArgs(args) || commandLine.hasHelpOption()) { + commandLine.printHelp(); + System.exit(1); + } else { + if (commandLine.hasAutotestOption()) { + verboseOutput("running autotests"); + _isAutotesting = true; + InstallationDriver autotestDriver = new InstallationDriver(commandLine); + autotestDriver.drive(); // ! reentrant into internalMain() + _isAutotesting = false; + ConsoleInstaller.message("\ncongratulations - autotests complete !"); + System.exit(0); + } + if (!useGui(commandLine)) { + verboseOutput("using the console installer"); + ConsoleInstaller consoleInstaller = new ConsoleInstaller(commandLine, jarInfo); + consoleInstaller.setTunnel(tunnel); + consoleInstaller.install(); + if (!isAutotesting()) { + System.exit(0); + } + } else { + verboseOutput("using the gui installer"); + new FrameInstaller(commandLine, jarInfo, autotest); + } + } + } catch (InstallationCancelledException ice) { + ConsoleInstaller.message((getText(TextKeys.INSTALLATION_CANCELLED))); + System.exit(1); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(1); + } + } + + private static void dumpSystemProperties() throws IOException { + if (isVerbose()) { + @SuppressWarnings("unchecked") + Enumeration names = (Enumeration)System.getProperties().propertyNames(); + StringBuilder contents = new StringBuilder(400); + contents.append("Properties at the beginning of the Jython installation:\n\n"); + while (names.hasMoreElements()) { + String name = names.nextElement(); + String value = System.getProperty(name, ""); + contents.append(name); + contents.append('='); + contents.append(value); + contents.append("\n"); + } + File output = File.createTempFile("System", ".properties"); + FileHelper.write(output, contents.toString()); + ConsoleInstaller.message("system properties dumped to " + output.getAbsolutePath()); + } + } + + private static void verboseOutput(String message) { + if (isVerbose()) { + ConsoleInstaller.message(message); + } + } + + private static void verboseOutput(Throwable t) { + if (isVerbose()) { + t.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/InstallationCancelledException.java b/installer/src/java/org/python/util/install/InstallationCancelledException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallationCancelledException.java @@ -0,0 +1,9 @@ +package org.python.util.install; + +public class InstallationCancelledException extends InstallerException { + + public InstallationCancelledException() { + super(); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/InstallationListener.java b/installer/src/java/org/python/util/install/InstallationListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallationListener.java @@ -0,0 +1,7 @@ +package org.python.util.install; + +public interface InstallationListener { + + public void progressFinished(); + +} diff --git a/installer/src/java/org/python/util/install/InstallationType.java b/installer/src/java/org/python/util/install/InstallationType.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallationType.java @@ -0,0 +1,120 @@ +package org.python.util.install; + +public class InstallationType { + + private boolean _installLibraryModules = true; + private boolean _installDemosAndExamples = true; + private boolean _installDocumentation = true; + private boolean _installSources = false; + private boolean _isStandalone = false; + + public boolean installLibraryModules() { + return _installLibraryModules; + } + + public boolean installDemosAndExamples() { + return _installDemosAndExamples; + } + + public boolean installDocumentation() { + return _installDocumentation; + } + + public boolean installSources() { + return _installSources; + } + + public void addLibraryModules() { + _installLibraryModules = true; + } + + public void removeLibraryModules() { + _installLibraryModules = false; + } + + public void addDemosAndExamples() { + _installDemosAndExamples = true; + } + + public void removeDemosAndExamples() { + _installDemosAndExamples = false; + } + + public void addDocumentation() { + _installDocumentation = true; + } + + public void removeDocumentation() { + _installDocumentation = false; + } + + public void addSources() { + _installSources = true; + } + + public void removeSources() { + _installSources = false; + } + + public void setStandalone() { + _isStandalone = true; + addLibraryModules(); + removeDemosAndExamples(); + removeDocumentation(); + removeSources(); + } + + public boolean isStandalone() { + return _isStandalone; + } + + public void setAll() { + addLibraryModules(); + addDemosAndExamples(); + addDocumentation(); + addSources(); + _isStandalone = false; + } + + public boolean isAll() { + return installLibraryModules() && installDemosAndExamples() && installDocumentation() && installSources(); + } + + public void setStandard() { + addLibraryModules(); + addDemosAndExamples(); + addDocumentation(); + removeSources(); + _isStandalone = false; + } + + public boolean isStandard() { + return installLibraryModules() && installDemosAndExamples() && installDocumentation() && !installSources(); + } + + public void setMinimum() { + removeLibraryModules(); + removeDemosAndExamples(); + removeDocumentation(); + removeSources(); + _isStandalone = false; + } + + public boolean isMinimum() { + return !installLibraryModules() && !installDemosAndExamples() && !installDocumentation() && !installSources(); + } + + /** + * @return true if current settings reflect one of the predefined settings + */ + public boolean isPredefined() { + return isAll() || isStandard() || isMinimum() || isStandalone(); + } + + public String toString() { + StringBuffer buf = new StringBuffer(30); + buf.append("mod: " + installDemosAndExamples() + ", demo: " + installDemosAndExamples() + ", doc: " + + installDocumentation() + ", src: " + installSources()); + return buf.toString(); + } +} diff --git a/installer/src/java/org/python/util/install/InstallerCommandLine.java b/installer/src/java/org/python/util/install/InstallerCommandLine.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallerCommandLine.java @@ -0,0 +1,456 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.MissingArgumentException; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.Parser; +import org.apache.commons.cli.PosixParser; + +public class InstallerCommandLine { + protected static final String INEXCLUDE_LIBRARY_MODULES = "mod"; + protected static final String INEXCLUDE_DEMOS_AND_EXAMPLES = "demo"; + protected static final String INEXCLUDE_DOCUMENTATION = "doc"; + protected static final String INEXCLUDE_SOURCES = "src"; + + protected static final String CONSOLE_SHORT = "c"; + protected static final String CONSOLE_LONG = "console"; + private static final String CONSOLE_DESC = "console based installation (user interaction)\n" + + "any other options will be ignored (except 'verbose')"; + + protected static final String SILENT_SHORT = "s"; + protected static final String SILENT_LONG = "silent"; + private static final String SILENT_DESC = "silent installation (without user interaction)"; + + protected static final String VERBOSE_SHORT = "v"; + protected static final String VERBOSE_LONG = "verbose"; + private static final String VERBOSE_DESC = "print more output during the installation\n" + + "(also valid in GUI and autotest mode)"; + + private static final String JRE_SHORT = "j"; + private static final String JRE_LONG = "jre"; + private static final String JRE_DESC = "home directory of the runtime jre or jdk\n" + + "(executables are assumed in the /bin subdirectory)\n" + "select this if you want to run Jython with a\n" + + "different java version than the installation"; + + private static final String AUTOTEST_SHORT = "A"; + private static final String AUTOTEST_LONG = "autotest"; + private static final String AUTOTEST_DESC = "automatic stress tests for the installer\n" + + "most of the other options are ignored\n" + "allowed additional options: '" + VERBOSE_LONG + "', '" + + JRE_LONG + "'"; + + private static final String DIRECTORY_SHORT = "d"; + private static final String DIRECTORY_LONG = "directory"; + private static final String DIRECTORY_DESC = "target directory to install to\n" + + "(required in silent mode,\nused as default in GUI mode)"; + + private static final String DIRECTORY_ARG = "dir"; + + private static final String TYPE_STANDARD = "standard"; + private static final String TYPE_ALL = "all"; + private static final String TYPE_MINIMUM = "minimum"; + protected static final String TYPE_STANDALONE = "standalone"; + private static final String STANDALONE_DOCUMENTATION = "install a single, executable .jar,\ncontaining all the modules"; + + private static final String INEXCLUDE_ARG = "part(s)"; + private static final String INEXCLUDE_PARTS = "more than one of the following is possible:\n" + "- " + + INEXCLUDE_LIBRARY_MODULES + ": library modules\n" + "- " + INEXCLUDE_DEMOS_AND_EXAMPLES + + ": demos and examples\n" + "- " + INEXCLUDE_DOCUMENTATION + ": documentation\n" + "- " + + INEXCLUDE_SOURCES + ": java source code"; + + private static final String TYPE_SHORT = "t"; + private static final String TYPE_LONG = "type"; + private static final String TYPE_ARG = TYPE_LONG; + private static final String TYPE_DESC = "installation type\n" + "one of the following types is possible\n" + + "(see also include/exclude parts):\n" + "- " + TYPE_ALL + ": everything (including " + INEXCLUDE_SOURCES + + ")\n" + "- " + TYPE_STANDARD + ": core, " + INEXCLUDE_LIBRARY_MODULES + ", " + + INEXCLUDE_DEMOS_AND_EXAMPLES + ", " + INEXCLUDE_DOCUMENTATION + ",\n"+ TYPE_STANDARD+ " is the default\n" + "- " + TYPE_MINIMUM + ": core\n" + + "- " + TYPE_STANDALONE + ": " + STANDALONE_DOCUMENTATION; + + private static final String INCLUDE_SHORT = "i"; + private static final String INCLUDE_LONG = "include"; + private static final String INCLUDE_DESC = "finer control over parts to install\n" + INEXCLUDE_PARTS; + + private static final String EXCLUDE_SHORT = "e"; + private static final String EXCLUDE_LONG = "exclude"; + private static final String EXCLUDE_DESC = "finer control over parts not to install\n" + INEXCLUDE_PARTS + + "\n(excludes override includes)"; + + private static final String HELP_SHORT = "h"; + private static final String HELP2_SHORT = "?"; + private static final String HELP_LONG = "help"; + private static final String HELP_DESC = "print this help (overrides any other options)"; + + private static final String SYNTAX = "\n\tjava -jar jython_version.jar"; + private static final String HEADER = "\nNo option at all will start the interactive GUI installer, except:\n" + + "Options respected in GUI mode are '" + DIRECTORY_LONG + "' and '" + JRE_LONG + + "', which serve as default values in the wizard.\n" + + "In non-GUI mode the following options are available:\n."; + private static final String SYNTAX_WITHOUT_JAR = "\n\tjava -jar "; + private static final String FOOTER = ""; + private static final String EXAMPLES = "\nexample of a GUI installation:{0}" + + "\n\nexample of a console installation:{0} -" + CONSOLE_SHORT + + "\n\nexample of a silent installation:{0} -" + SILENT_SHORT + " -" + DIRECTORY_SHORT + " targetDirectory" + + "\n\nexamples of a silent installation with more options:{0} -" + SILENT_SHORT + " -" + DIRECTORY_SHORT + + " targetDirectory -" + TYPE_SHORT + " " + TYPE_MINIMUM + " -" + INCLUDE_SHORT + " " + INEXCLUDE_SOURCES + + " -" + JRE_SHORT + " javaHome" + "{0} -" + SILENT_SHORT + " -" + DIRECTORY_SHORT + " targetDirectory -" + + TYPE_SHORT + " " + TYPE_STANDARD + " -" + EXCLUDE_SHORT + " " + INEXCLUDE_DEMOS_AND_EXAMPLES + " " + + INEXCLUDE_DOCUMENTATION + "\n\t\t -" + INCLUDE_SHORT + " " + INEXCLUDE_SOURCES + " -" + JRE_SHORT + + " javaHome -" + VERBOSE_SHORT + + "\n\nexample of an autotest installation into temporary directories:{0} -" + AUTOTEST_SHORT + + "\n\t(make sure you do NOT touch mouse NOR keyboard after hitting enter/return!)" + + "\n\nexample of an autotest installation, using a different jre for the start scripts:{0} -" + + AUTOTEST_SHORT + " -" + JRE_SHORT + " javaHome" + " -" + VERBOSE_SHORT + + "\n\t(make sure you do NOT touch mouse NOR keyboard after hitting enter/return!)"; + + private String[] _args; + private Options _options; + private CommandLine _commandLine; + private JarInfo _jarInfo; + private final Parser _parser = new PosixParser(); + + public InstallerCommandLine(JarInfo jarInfo) { + createOptions(); + _jarInfo = jarInfo; + } + + /** + * Pre-scan of the arguments to detect a verbose flag + * @param args + * @return true if there is a verbose option + */ + public static final boolean hasVerboseOptionInArgs(String[] args) { + String shortVerbose = "-".concat(VERBOSE_SHORT); + String longVerbose = "--".concat(VERBOSE_LONG); + return hasOptionInArgs(args, shortVerbose, longVerbose); + } + + /** + * constructor intended for JUnit tests only. + */ + public InstallerCommandLine() { + this(null); + } + + /** + * Set the arguments from the command line. + * + * @param args the arguments of the command line + * @return true if all arguments are valid, false otherwise. No help is printed if + * false is returned + */ + public boolean setArgs(String args[]) { + // pre-process args to determine if we can (and should) switch to console mode + try { + CommandLine preCommandLine = _parser.parse(_options, args, false); + if (!hasConsoleOption(preCommandLine) && !hasSilentOption(preCommandLine) + && !hasAutotestOption(preCommandLine)) { + if (!Installation.isGuiAllowed() || Installation.isGNUJava()) { + // auto switch to console mode + if (hasVerboseOption(preCommandLine)) { + ConsoleInstaller.message("auto-switching to console mode"); + } + String[] newArgs = new String[args.length + 1]; + System.arraycopy(args, 0, newArgs, 0, args.length); + newArgs[args.length] = "-" + CONSOLE_SHORT; + args = newArgs; + } + } + } catch (Exception e) { + // ignore + } + _args = args; + try { + // throws for missing or unknown options / arguments + _commandLine = _parser.parse(_options, _args, false); + } catch (MissingArgumentException mae) { + System.err.println(mae.getMessage()); + return false; + } catch (ParseException pe) { + System.err.println(pe.getMessage()); + return false; + } + List unrecognized = _commandLine.getArgList(); + if (unrecognized.size() > 0) { + System.err.println("unrecognized argument(s): " + unrecognized); + return false; + } + if (hasTypeOption()) { + String type = _commandLine.getOptionValue(TYPE_SHORT); + if (TYPE_ALL.equals(type) || TYPE_STANDARD.equals(type) || TYPE_MINIMUM.equals(type) + || TYPE_STANDALONE.equals(type)) { + } else { + System.err.println("unrecognized argument '" + type + "' to option: " + TYPE_SHORT + " / " + TYPE_LONG); + return false; + } + } + if (hasSilentOption()) { + if (!hasDirectoryOption()) { + System.err.println("option " + DIRECTORY_SHORT + " / " + DIRECTORY_LONG + " is required in " + + SILENT_LONG + " mode"); + return false; + } + } + if (hasIncludeOption()) { + String[] includeParts = _commandLine.getOptionValues(INCLUDE_SHORT); + for (int i = 0; i < includeParts.length; i++) { + if (!isValidInExcludePart(includeParts[i])) { + System.err.println("unrecognized include part '" + includeParts[i] + "'"); + return false; + } + } + } + if (hasExcludeOption()) { + String[] excludeParts = _commandLine.getOptionValues(EXCLUDE_SHORT); + for (int i = 0; i < excludeParts.length; i++) { + if (!isValidInExcludePart(excludeParts[i])) { + System.err.println("unrecognized exclude part '" + excludeParts[i] + "'"); + return false; + } + } + } + return true; + } + + public boolean hasArguments() { + return _args.length > 0; + } + + public boolean hasHelpOption() { + return _commandLine.hasOption(HELP_SHORT) || _commandLine.hasOption(HELP2_SHORT) + || _commandLine.hasOption(HELP_LONG); + } + + public boolean hasSilentOption() { + return hasSilentOption(_commandLine); + } + + private boolean hasSilentOption(CommandLine commandLine) { + return commandLine.hasOption(SILENT_SHORT) || commandLine.hasOption(SILENT_LONG); + } + + public boolean hasConsoleOption() { + return hasConsoleOption(_commandLine); + } + + private boolean hasConsoleOption(CommandLine commandLine) { + return commandLine.hasOption(CONSOLE_SHORT) || commandLine.hasOption(CONSOLE_LONG); + } + + public boolean hasAutotestOption() { + return hasAutotestOption(_commandLine); + } + + private boolean hasAutotestOption(CommandLine commandLine) { + return commandLine.hasOption(AUTOTEST_SHORT) || commandLine.hasOption(AUTOTEST_LONG); + } + + public boolean hasDirectoryOption() { + return _commandLine.hasOption(DIRECTORY_SHORT) || _commandLine.hasOption(DIRECTORY_LONG); + } + + public boolean hasTypeOption() { + return _commandLine.hasOption(TYPE_SHORT) || _commandLine.hasOption(TYPE_LONG); + } + + public boolean hasIncludeOption() { + return _commandLine.hasOption(INCLUDE_SHORT) || _commandLine.hasOption(INCLUDE_LONG); + } + + public boolean hasExcludeOption() { + return _commandLine.hasOption(EXCLUDE_SHORT) || _commandLine.hasOption(EXCLUDE_LONG); + } + + public boolean hasJavaHomeOption() { + return _commandLine.hasOption(JRE_SHORT) || _commandLine.hasOption(JRE_LONG); + } + + public boolean hasVerboseOption() { + return hasVerboseOption(_commandLine); + } + + private boolean hasVerboseOption(CommandLine commandLine) { + return commandLine.hasOption(VERBOSE_SHORT) || commandLine.hasOption(VERBOSE_LONG); + } + + public void printHelp() { + HelpFormatter formatter = new HelpFormatter(); + formatter.defaultWidth = 76; + String syntax = SYNTAX; + if (_jarInfo != null) { + try { + syntax = SYNTAX_WITHOUT_JAR + _jarInfo.getJarFile().getName(); + } catch (IOException ioe) { + } + } + formatter.printHelp(syntax, HEADER, _options, FOOTER, true); + String examples = MessageFormat.format(EXAMPLES, syntax); + System.out.println(examples); + } + + /** + * @return the requested target directory, null if no directory specified + */ + public File getTargetDirectory() { + if (hasDirectoryOption()) { + return new File(_commandLine.getOptionValue(DIRECTORY_SHORT)); + } else { + return null; + } + } + + /** + * @return a java home handler for the requested java home directory, or a default handler if no + * java home specified + */ + public JavaHomeHandler getJavaHomeHandler() { + if (hasJavaHomeOption()) { + return new JavaHomeHandler(_commandLine.getOptionValue(JRE_SHORT)); + } else { + return new JavaHomeHandler(); + } + } + + /** + * The Installation type is built out of the type, include and exclude option + * + * @return the installation type usable for the jar installer + */ + public InstallationType getInstallationType() { + InstallationType installationType = new InstallationType(); // defaults to standard + // build a priori values out of the type option + if (hasTypeOption()) { + String typeName = _commandLine.getOptionValue(TYPE_SHORT); + if (TYPE_ALL.equals(typeName)) { + installationType.setAll(); + } else if (TYPE_MINIMUM.equals(typeName)) { + installationType.setMinimum(); + } else if (TYPE_STANDALONE.equals(typeName)) { + installationType.setStandalone(); + } + } + // add parts to include + if (hasIncludeOption()) { + String[] includeParts = _commandLine.getOptionValues(INCLUDE_SHORT); + for (int i = 0; i < includeParts.length; i++) { + if (INEXCLUDE_DEMOS_AND_EXAMPLES.equals(includeParts[i])) { + installationType.addDemosAndExamples(); + } + if (INEXCLUDE_DOCUMENTATION.equals(includeParts[i])) { + installationType.addDocumentation(); + } + if (INEXCLUDE_LIBRARY_MODULES.equals(includeParts[i])) { + installationType.addLibraryModules(); + } + if (INEXCLUDE_SOURCES.equals(includeParts[i])) { + installationType.addSources(); + } + } + } + // remove parts to exclude + if (hasExcludeOption()) { + String[] excludeParts = _commandLine.getOptionValues(EXCLUDE_SHORT); + for (int i = 0; i < excludeParts.length; i++) { + if (INEXCLUDE_DEMOS_AND_EXAMPLES.equals(excludeParts[i])) { + installationType.removeDemosAndExamples(); + } + if (INEXCLUDE_DOCUMENTATION.equals(excludeParts[i])) { + installationType.removeDocumentation(); + } + if (INEXCLUDE_LIBRARY_MODULES.equals(excludeParts[i])) { + installationType.removeLibraryModules(); + } + if (INEXCLUDE_SOURCES.equals(excludeParts[i])) { + installationType.removeSources(); + } + } + } + return installationType; + } + + // + // private methods + // + + private static final boolean hasOptionInArgs(String[] args, String shortOption, String longOption) { + boolean hasOption = false; + int i = 0; + while (!hasOption && i < args.length) { + if (shortOption.equals(args[i]) || longOption.equals(args[i])) { + hasOption = true; + } + i++; + } + return hasOption; + } + + private void createOptions() { + _options = new Options(); + _options.setSortAsAdded(true); + + // console or silent mode + Option consoleOption = new Option(CONSOLE_SHORT, CONSOLE_LONG, false, CONSOLE_DESC); + Option silentOption = new Option(SILENT_SHORT, SILENT_LONG, false, SILENT_DESC); + Option autotestOption = new Option(AUTOTEST_SHORT, AUTOTEST_LONG, false, AUTOTEST_DESC); + OptionGroup group1 = new OptionGroup(); + group1.addOption(consoleOption); + group1.addOption(silentOption); + group1.addOption(autotestOption); + _options.addOptionGroup(group1); + + // target directory + Option directoryOption = new Option(DIRECTORY_SHORT, DIRECTORY_LONG, true, DIRECTORY_DESC); + directoryOption.setArgName(DIRECTORY_ARG); + _options.addOption(directoryOption); + + // installation type + Option typeOption = new Option(TYPE_SHORT, TYPE_LONG, true, TYPE_DESC); + typeOption.setArgName(TYPE_ARG); + _options.addOption(typeOption); + + // additional parts to include + Option includeOption = new Option(INCLUDE_SHORT, INCLUDE_DESC); + includeOption.setArgs(4); + includeOption.setArgName(INEXCLUDE_ARG); + includeOption.setLongOpt(INCLUDE_LONG); + _options.addOption(includeOption); + + // parts to exclude + Option excludeOption = new Option(EXCLUDE_SHORT, EXCLUDE_DESC); + excludeOption.setArgs(4); + excludeOption.setArgName(INEXCLUDE_ARG); + excludeOption.setLongOpt(EXCLUDE_LONG); + _options.addOption(excludeOption); + + // runtime jre + Option jreOption = new Option(JRE_SHORT, JRE_LONG, true, JRE_DESC); + jreOption.setArgName(DIRECTORY_ARG); + _options.addOption(jreOption); + + // verbose + Option verboseOption = new Option(VERBOSE_SHORT, VERBOSE_LONG, false, VERBOSE_DESC); + _options.addOption(verboseOption); + + // different help options + Option helpHOption = new Option(HELP_SHORT, HELP_LONG, false, HELP_DESC); + Option helpQOption = new Option(HELP2_SHORT, HELP_DESC); + OptionGroup group2 = new OptionGroup(); + group2.addOption(helpHOption); + group2.addOption(helpQOption); + _options.addOptionGroup(group2); + } + + private boolean isValidInExcludePart(String part) { + return INEXCLUDE_DEMOS_AND_EXAMPLES.equals(part) || INEXCLUDE_DOCUMENTATION.equals(part) + || INEXCLUDE_LIBRARY_MODULES.equals(part) || INEXCLUDE_SOURCES.equals(part); + } + +} diff --git a/installer/src/java/org/python/util/install/InstallerException.java b/installer/src/java/org/python/util/install/InstallerException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/InstallerException.java @@ -0,0 +1,21 @@ +package org.python.util.install; + +public class InstallerException extends RuntimeException { + + public InstallerException() { + super(); + } + + public InstallerException(String message) { + super(message); + } + + public InstallerException(String message, Throwable cause) { + super(message, cause); + } + + public InstallerException(Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/JarInfo.java b/installer/src/java/org/python/util/install/JarInfo.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JarInfo.java @@ -0,0 +1,235 @@ +package org.python.util.install; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.StringTokenizer; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class JarInfo { + private static final String JAR_URL_PREFIX = "jar:file:"; + private static final String JAR_SEPARATOR = "!"; + private static final String JYTHON = "Jython"; + private static final String VERSION_ATTRIBUTE = "version"; + private static final String EXCLUDE_DIRS_ATTRIBUTE = "exclude-dirs"; + private static final String EXCLUDE_DIRS_DELIM = ";"; + + private File _jarFile; + private int _numberOfEntries; + private Manifest _manifest; + private String _licenseText; + private String _readmeText; + + public JarInfo() { + _jarFile = null; + _numberOfEntries = 0; + _manifest = null; + + try { + readJarInfo(); + } catch (IOException ioe) { + throw new InstallerException(Installation.getText(TextKeys.ERROR_ACCESS_JARFILE), ioe); + } + } + + public String getVersion() { + String version = ""; + try { + Attributes jythonAttributes = getManifest().getAttributes(JYTHON); + if (jythonAttributes != null) { + version = jythonAttributes.getValue(VERSION_ATTRIBUTE); // do + // not + // use + // containsKey + } + } catch (IOException ioe) { + } + return version; + } + + public File getJarFile() throws IOException { + if (_jarFile == null) + readJarInfo(); + return _jarFile; + } + + public Manifest getManifest() throws IOException { + if (_manifest == null) + readJarInfo(); + return _manifest; + } + + public int getNumberOfEntries() throws IOException { + if (_numberOfEntries == 0) + readJarInfo(); + return _numberOfEntries; + } + + public List getExcludeDirs() throws IOException { + List excludeDirs = new ArrayList(); + Attributes jythonAttributes = getManifest().getAttributes(JYTHON); + if (jythonAttributes != null) { + // do not use containsKey + String excludeDirsString = jythonAttributes.getValue(EXCLUDE_DIRS_ATTRIBUTE); + if (excludeDirsString != null && excludeDirsString.length() > 0) { + StringTokenizer tokenizer = new StringTokenizer(excludeDirsString, EXCLUDE_DIRS_DELIM); + while (tokenizer.hasMoreTokens()) { + excludeDirs.add(tokenizer.nextToken()); + } + } + } + return excludeDirs; + } + + public String getLicenseText() throws IOException { + if (_licenseText == null) { + readJarInfo(); + } + return _licenseText; + } + + public String getReadmeText() throws IOException { + if (_readmeText == null) { + readJarInfo(); + } + return _readmeText; + } + + private void readJarInfo() throws IOException { + String fullClassName = getClass().getName(); + String className = fullClassName.substring(fullClassName.lastIndexOf(".") + 1); + URL url = getClass().getResource(className + ".class"); + // we expect an URL like: + // jar:file:/C:/stuff/jython21i.jar!/org/python/util/install/JarInfo.class + // escape plus signs, since the URLDecoder would turn them into spaces + final String plus = "\\+"; + final String escapedPlus = "__ppluss__"; + String rawUrl = url.toString(); + rawUrl = rawUrl.replaceAll(plus, escapedPlus); + String urlString = URLDecoder.decode(rawUrl, "UTF-8"); + urlString = urlString.replaceAll(escapedPlus, plus); + int jarSeparatorIndex = urlString.lastIndexOf(JAR_SEPARATOR); + if (!urlString.startsWith(JAR_URL_PREFIX) || jarSeparatorIndex <= 0) { + throw new InstallerException(Installation.getText(TextKeys.UNEXPECTED_URL, urlString)); + } + String jarFileName = urlString.substring(JAR_URL_PREFIX.length(), jarSeparatorIndex); + _jarFile = new File(jarFileName); + if (!_jarFile.exists()) { + throw new InstallerException(Installation.getText(TextKeys.JAR_NOT_FOUND, _jarFile.getAbsolutePath())); + } + JarFile jarFile = new JarFile(_jarFile); + Enumeration entries = jarFile.entries(); + _numberOfEntries = 0; + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + if ("LICENSE.txt".equals(entry.getName())) { + _licenseText = readTextFile(entry, jarFile); + } + if ("README.txt".equals(entry.getName())) { + _readmeText = readTextFile(entry, jarFile); + } + _numberOfEntries++; + } + _manifest = jarFile.getManifest(); + if (_manifest == null) { + throw new InstallerException(Installation.getText(TextKeys.NO_MANIFEST, _jarFile.getAbsolutePath())); + } + jarFile.close(); + } + + /** + * Read the text file with the most appropriate Charset. + * + * @param entry + * @param jarFile + * + * @return the contents of the text file + * + * @throws IOException + */ + private String readTextFile(JarEntry entry, JarFile jarFile) throws IOException { + String contents = readTextFileWithCharset(jarFile, entry, "US-ASCII"); // expected to run on most platforms + if (contents == null) { + contents = readTextFileWithCharset(jarFile, entry, "ISO-8859-1"); + } + if (contents == null) { + contents = readTextFileWithDefaultCharset(jarFile, entry); + } + return contents; + } + + /** + * Try to read the text file (jarEntry) from the jarFile, using a given charsetName. + * + * @param jarFile + * @param entry + * @param charsetName the name of the Charset + * + * @return the contents of the text file as String (if reading was successful), null otherwise.
+ * No exception is thrown + */ + private String readTextFileWithCharset(JarFile jarFile, JarEntry entry, String charsetName) { + String contents = null; + if (Charset.isSupported(charsetName)) { + BufferedReader reader = null; + try { + StringBuffer buffer = new StringBuffer(1000); + reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry), Charset + .forName(charsetName))); + buffer = new StringBuffer(1000); + for (String s; (s = reader.readLine()) != null;) { + buffer.append(s); + buffer.append("\n"); + } + contents = buffer.toString(); + } catch (IOException ioe) { + } finally { + if (reader != null) + try { + reader.close(); + } catch (IOException e) { + } + } + } + return contents; + } + + /** + * Read the text file (jarEntry) from the jarFile, using the platform default Charset. + * + * @param jarFile + * @param entry + * + * @return the contents of the text file as String. + * + * @throws IOException if a problem occurs + */ + private String readTextFileWithDefaultCharset(JarFile jarFile, JarEntry entry) throws IOException { + String contents = null; + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry))); + StringBuffer buffer = new StringBuffer(1000); + for (String s; (s = reader.readLine()) != null;) { + buffer.append(s); + buffer.append("\n"); + } + contents = buffer.toString(); + } finally { + if (reader != null) + reader.close(); + } + return contents; + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/JarInstaller.java b/installer/src/java/org/python/util/install/JarInstaller.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JarInstaller.java @@ -0,0 +1,283 @@ +package org.python.util.install; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Working horse extracting the contents of the installation .jar to the file system.
+ * The directory stucture is preserved, but there is the possibility to exclude some entries + * (directories at the moment). + */ +public class JarInstaller { + + public static final String JYTHON_JAR = "jython.jar"; + + private static final String PATH_SEPARATOR = "/"; + + private static final String LIB_NAME_SEP = "Lib" + PATH_SEPARATOR; + + private static final String LIB_PAWT_SEP = LIB_NAME_SEP + "pawt" + PATH_SEPARATOR; + + private static final int BUFFER_SIZE = 1024; + + private ProgressListener _progressListener; + + private JarInfo _jarInfo; + + private List _installationListeners; + + public JarInstaller(ProgressListener progressListener, JarInfo jarInfo) { + _progressListener = progressListener; + _jarInfo = jarInfo; + _installationListeners = new ArrayList(); + } + + /** + * Do the pysical installation: + *

    + *
  • unzip the files + *
  • generate the start scripts + *
+ * + * @param targetDirectory + * @param installationType + */ + public void inflate(final File targetDirectory, InstallationType installationType, JavaHomeHandler javaHomeHandler) { + try { + // has to correspond with build.xml + // has to correspond with build.Lib.include.properties + List excludeDirs = _jarInfo.getExcludeDirs(); + List coreLibFiles = new ArrayList(); + if (!installationType.installSources()) { + excludeDirs.add("src"); + excludeDirs.add("grammar"); + excludeDirs.add("extlibs"); + } + if (!installationType.installDocumentation()) { + excludeDirs.add("Doc"); + } + if (!installationType.installDemosAndExamples()) { + excludeDirs.add("Demo"); + } + if (!installationType.installLibraryModules()) { + excludeDirs.add(LIB_NAME_SEP + "email"); + excludeDirs.add(LIB_NAME_SEP + "encodings"); + excludeDirs.add(LIB_NAME_SEP + "test"); + excludeDirs.add(LIB_NAME_SEP + "jxxload_help"); + coreLibFiles = getCoreLibFiles(); + } + if (installationType.isStandalone()) { + excludeDirs.add("Tools"); + excludeDirs.add(LIB_NAME_SEP + "email/test"); + excludeDirs.add(LIB_NAME_SEP + "test"); + } + int count = 0; + int percent = 0; + int numberOfIntervals = 100 / _progressListener.getInterval(); + int numberOfEntries = approximateNumberOfEntries(installationType); + int threshold = numberOfEntries / numberOfIntervals + 1; // +1 = pessimistic + boolean coreExclusionReported = false; + // unzip + ZipInputStream zipInput = new ZipInputStream(new BufferedInputStream(new FileInputStream(_jarInfo.getJarFile()), + BUFFER_SIZE)); + ZipEntry zipEntry = zipInput.getNextEntry(); + while (zipEntry != null) { + String zipEntryName = zipEntry.getName(); + boolean exclude = false; + // handle exclusion of directories + Iterator excludeDirsAsIterator = excludeDirs.iterator(); + while (excludeDirsAsIterator.hasNext()) { + if (zipEntryName.startsWith(excludeDirsAsIterator.next() + + PATH_SEPARATOR)) { + exclude = true; + } + } + // exclude build.xml when not installing source + if (!installationType.installSources() && zipEntryName.equals("build.xml")) + exclude = true; + // handle exclusion of core Lib files + if (!exclude) { + exclude = shouldExcludeFile(installationType, + coreLibFiles, + zipEntry, + zipEntryName); + if (Installation.isVerbose() && !coreExclusionReported && exclude) { + ConsoleInstaller.message("excluding some .py files, like " + zipEntryName); + coreExclusionReported = true; + } + } + if (exclude) { + if (Installation.isVerbose() && zipEntry.isDirectory()) { + ConsoleInstaller.message("excluding directory " + zipEntryName); + } + } else { + count++; + if (count % threshold == 0) { + percent = percent + _progressListener.getInterval(); + _progressListener.progressChanged(percent); + } + createDirectories(targetDirectory, zipEntryName); + if (!zipEntry.isDirectory()) { + File file = createFile(targetDirectory, zipEntryName); + _progressListener.progressEntry(file.getAbsolutePath()); + FileOutputStream output = new FileOutputStream(file); + byte[] buffer = new byte[BUFFER_SIZE]; + int len; + while ((len = zipInput.read(buffer)) > 0) { + output.write(buffer, 0, len); + } + output.close(); + file.setLastModified(zipEntry.getTime()); + } + } + zipInput.closeEntry(); + zipEntry = zipInput.getNextEntry(); + } + if (!installationType.isStandalone()) { + // generate start scripts + _progressListener.progressStartScripts(); + StartScriptGenerator generator = new StartScriptGenerator(targetDirectory, javaHomeHandler); + generator.generateStartScripts(); + } else { + _progressListener.progressStandalone(); + File jythonJar = new File(targetDirectory, JYTHON_JAR); + File jythonPlainJar = new File(targetDirectory, "plain_" + JYTHON_JAR); + jythonJar.renameTo(jythonPlainJar); + File libDir = new File(targetDirectory, "Lib"); + StandalonePackager packager = new StandalonePackager(jythonJar); + packager.addJarFile(jythonPlainJar); + _progressListener.progressChanged(90); // approx + packager.addFullDirectory(libDir); + packager.close(); + // TODO:Oti move to FileHelper + StandalonePackager.emptyDirectory(targetDirectory, jythonJar); + } + // finish: inform listeners + _progressListener.progressFinished(); + Iterator installationListenersIterator = _installationListeners.iterator(); + while (installationListenersIterator.hasNext()) { + installationListenersIterator.next().progressFinished(); + } + } catch (IOException ioe) { + throw new InstallerException(Installation.getText(TextKeys.ERROR_ACCESS_JARFILE), ioe); + } + } + + public void addInstallationListener(InstallationListener installationListener) { + if (installationListener != null) { + _installationListeners.add(installationListener); + } + } + + private int approximateNumberOfEntries(InstallationType installationType) { + int numberOfEntries = 200; // core (minimum) + if (installationType.installLibraryModules()) { + if (installationType.isStandalone()) { + numberOfEntries += 450; + } else { + numberOfEntries += 1300; + } + } + if (installationType.installDemosAndExamples()) { + numberOfEntries += 70; + } + if (installationType.installDocumentation()) { + numberOfEntries += 500; + } + if (installationType.installSources()) { + numberOfEntries += 1000; + } + return numberOfEntries; + } + + private void createDirectories(final File targetDirectory, final String zipEntryName) { + int lastSepIndex = zipEntryName.lastIndexOf(PATH_SEPARATOR); + if (lastSepIndex > 0) { + File directory = new File(targetDirectory, zipEntryName.substring(0, lastSepIndex)); + if (directory.exists() && directory.isDirectory()) {} else { + if (!directory.mkdirs()) { + throw new InstallerException(Installation.getText(TextKeys.UNABLE_CREATE_DIRECTORY, + directory.getAbsolutePath())); + } + } + } + } + + private File createFile(final File targetDirectory, final String zipEntryName) + throws IOException { + File file = new File(targetDirectory, zipEntryName); + if (file.exists() && file.isFile()) {} else { + if (!file.createNewFile()) { + throw new InstallerException(Installation.getText(TextKeys.UNABLE_CREATE_FILE, + file.getCanonicalPath())); + } + } + return file; + } + + private List getCoreLibFiles() { + List coreLibFiles = new ArrayList(); + coreLibFiles.add("__future__.py"); + coreLibFiles.add("copy.py"); + coreLibFiles.add("dbexts.py"); + coreLibFiles.add("imaplib.py"); + coreLibFiles.add("isql.py"); + coreLibFiles.add("javaos.py"); + coreLibFiles.add("javapath.py"); + coreLibFiles.add("jreload.py"); + coreLibFiles.add("marshal.py"); + coreLibFiles.add("ntpath.py"); + coreLibFiles.add("os.py"); + coreLibFiles.add("popen2.py"); + coreLibFiles.add("posixpath.py"); + coreLibFiles.add("random.py"); + coreLibFiles.add("re.py"); + coreLibFiles.add("site.py"); + coreLibFiles.add("socket.py"); + coreLibFiles.add("sre.py"); + coreLibFiles.add("sre_compile.py"); + coreLibFiles.add("sre_constants.py"); + coreLibFiles.add("sre_parse.py"); + coreLibFiles.add("stat.py"); + coreLibFiles.add("string.py"); + coreLibFiles.add("threading.py"); + coreLibFiles.add("UserDict.py"); + coreLibFiles.add("zipfile.py"); + coreLibFiles.add("zlib.py"); + return coreLibFiles; + } + + private boolean shouldExcludeFile(InstallationType installationType, + List coreLibFiles, + ZipEntry zipEntry, + String zipEntryName) { + boolean exclude = false; + if (!installationType.installLibraryModules()) { + // handle files in Lib + if (!zipEntry.isDirectory() && zipEntryName.startsWith(LIB_NAME_SEP)) { + // include all files in /pawt subdirectory + if (!zipEntryName.startsWith(LIB_PAWT_SEP)) { + if (zipEntryName.endsWith(".py")) { // only compare *.py files + exclude = true; + Iterator coreLibFilesAsIterator = coreLibFiles.iterator(); + while (coreLibFilesAsIterator.hasNext()) { + String coreFileName = coreLibFilesAsIterator.next(); + if (zipEntryName.endsWith(PATH_SEPARATOR + coreFileName)) { + exclude = false; + } + } + } + } + } + } + return exclude; + } +} diff --git a/installer/src/java/org/python/util/install/JavaHomeHandler.java b/installer/src/java/org/python/util/install/JavaHomeHandler.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaHomeHandler.java @@ -0,0 +1,209 @@ +package org.python.util.install; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * Unified entry point for treatment of java.home + *

+ * Note that the system property java.home is never changed + */ +public final class JavaHomeHandler { + + public static final String JAVA_HOME = "java.home"; + + private static final String JAVA = "java"; + + private static final String JAVA_EXE = "java.exe"; + + private static final String BIN = "bin"; + + private static final String DEFAULT = "_default_"; + + private static final String EMPTY = ""; + + /** + * A map for java home strings and their respective executable names + */ + private static Map _executableNames; + + /** + * The current java home + */ + private String _currentJavaHome; + + /** + * create a java home handler for the default java home + */ + public JavaHomeHandler() { + this(DEFAULT); + } + + /** + * create a java home handler for a java home deviation + * + * @param currentJavaHome + * The deviated java home + */ + public JavaHomeHandler(String currentJavaHome) { + setCurrentJavaHome(currentJavaHome); + check(getCurrentJavaHome()); + } + + /** + * get the name of the java executable + * + * @return A name of a java executable which can be passed to {@link ChildProcess} + */ + public String getExecutableName() { + return getExecutableName(getCurrentJavaHome()); + } + + /** + * tell the validity of the current java home + *

+ * Note: if the current java home is not valid, {@link JavaHomeHandler#getExecutableName()} + * still returns the name of a callable java + * + * @return true if we have a valid java home, false otherwise. + */ + public boolean isValidHome() { + return !getFallbackExecutableName().equals(getExecutableName()); + } + + /** + * get the current java home, if it is valid + * + * @return The current java home + * @throws InstallerException + * if there is no valid java home + * + * @see JavaHomeHandler#isValidHome() + */ + public File getHome() throws InstallerException { + if (!isValidHome()) { + throw new InstallerException("no valid java home"); + } else { + return new File(getCurrentJavaHome()); + } + } + + /** + * @return true if the current java home is a deviation, false + * otherwise + */ + public boolean isDeviation() { + // make sure the default java home is also known + if (!getExecutableNames().containsKey(DEFAULT)) { + check(DEFAULT); + } + return !getExecutableName(DEFAULT).equals(getExecutableName(getCurrentJavaHome())); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(80); + builder.append("["); + if(!isValidHome()) { + builder.append("in"); + } + builder.append("valid java home: "); + builder.append(getCurrentJavaHome()); + builder.append("; executable: "); + builder.append(getExecutableName()); + builder.append("]"); + return builder.toString(); + } + + /** + * reset the handler (clear all stored java homes) + */ + static void reset() { + getExecutableNames().clear(); + } + + private String getExecutableName(String javaHome) { + Map executableNames = getExecutableNames(); + if (!executableNames.containsKey(javaHome)) { + check(javaHome); + } + return executableNames.get(javaHome); + } + + private void check(String javaHome) { + boolean valid = false; + boolean isDefault = false; + File javaExecutableFile = null; + if (DEFAULT.equals(javaHome)) { + isDefault = true; + javaHome = System.getProperty(JAVA_HOME, EMPTY); + } + if (javaHome.length() > 0) { + File javaHomeDir = new File(javaHome); + if (javaHomeDir.exists() && javaHomeDir.isDirectory()) { + File binDir = new File(javaHomeDir, BIN); + if (binDir.exists() && binDir.isDirectory()) { + javaExecutableFile = getExecutableFile(binDir); + if (javaExecutableFile.exists()) { + valid = true; + } + } + } + } + if (valid) { + addExecutable(javaHome, javaExecutableFile); + if (isDefault) { + addExecutable(DEFAULT, javaExecutableFile); + if (DEFAULT.equals(getCurrentJavaHome())) { + // update the current home to the real one + setCurrentJavaHome(javaHome); + } + } + } else { + addFallbackExecutable(javaHome); + if (isDefault) { + addFallbackExecutable(DEFAULT); + } + } + } + + private String getFallbackExecutableName() { + return JAVA; + } + + private void addFallbackExecutable(String javaHome) { + getExecutableNames().put(javaHome, getFallbackExecutableName()); + } + + private void addExecutable(String javaHome, File javaExecutableFile) { + getExecutableNames().put(javaHome, javaExecutableFile.getAbsolutePath()); + } + + private File getExecutableFile(File binDir) { + if (Installation.isWindows()) { + return new File(binDir, JAVA_EXE); + } else { + return new File(binDir, JAVA); + } + } + + private static Map getExecutableNames() { + if (_executableNames == null) { + _executableNames = new HashMap(); + } + return _executableNames; + } + + private String getCurrentJavaHome() { + if (_currentJavaHome == null) { + return DEFAULT; + } else { + return _currentJavaHome; + } + } + + private void setCurrentJavaHome(String currentJavaHome) { + _currentJavaHome = currentJavaHome.trim(); + } +} diff --git a/installer/src/java/org/python/util/install/JavaSelectionPage.java b/installer/src/java/org/python/util/install/JavaSelectionPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaSelectionPage.java @@ -0,0 +1,199 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.File; + +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; + +public class JavaSelectionPage extends AbstractWizardPage { + + private static final long serialVersionUID = 2871052924519223110L; + + private final static String _CURRENT_ACTION_COMMAND = "current"; + private final static String _OTHER_ACTION_COMMAND = "other"; + + private JRadioButton _currentButton; + private JRadioButton _otherButton; + + private JLabel _label; + private JTextField _javaHome; + private JButton _browse; + + public JavaSelectionPage() { + super(); + initComponents(); + } + + private void initComponents() { + // label for java home + _label = new JLabel(); + + // radio buttons + RadioButtonListener radioButtonListener = new RadioButtonListener(); + _currentButton = new JRadioButton(); + _currentButton.setActionCommand(_CURRENT_ACTION_COMMAND); + _currentButton.addActionListener(radioButtonListener); + _otherButton = new JRadioButton(); + _otherButton.setActionCommand(_OTHER_ACTION_COMMAND); + _otherButton.addActionListener(radioButtonListener); + ButtonGroup radioButtonGroup = new ButtonGroup(); + radioButtonGroup.add(_currentButton); + radioButtonGroup.add(_otherButton); + JPanel radioPanel = new JPanel(new GridLayout(0, 1)); + radioPanel.add(_currentButton); + radioPanel.add(_otherButton); + + // directory for java home + _javaHome = new JTextField(40); + _javaHome.addFocusListener(new JavaFocusListener()); + // browse button + _browse = new JButton(); + _browse.addActionListener(new BrowseButtonListener()); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panel.add(_label, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(radioPanel, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + panel.add(_javaHome, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + panel.add(_browse, gridBagConstraints); + + add(panel); + } + + JTextField getJavaHome() { + return _javaHome; + } + + protected String getTitle() { + return getText(TARGET_JAVA_HOME_PROPERTY); + } + + protected String getDescription() { + return getText(CHOOSE_JRE); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _currentButton; + } + + protected void activate() { + _label.setText(getText(SELECT_JAVA_HOME) + ": "); + _currentButton.setText(getText(CURRENT)); + _otherButton.setText(getText(OTHER)); + _browse.setText(getText(BROWSE)); + setValues(); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private void setValues() { + boolean current = true; + JavaHomeHandler javaHomeHandler = FrameInstaller.getJavaHomeHandler(); + if (javaHomeHandler.isDeviation()) { + current = false; + } + setCurrent(current); + } + + private void setCurrent(boolean current) { + if (current) { + FrameInstaller.setJavaHomeHandler(new JavaHomeHandler()); + _currentButton.setSelected(true); + _otherButton.setSelected(false); + _javaHome.setEnabled(false); + _browse.setEnabled(false); + } else { + _currentButton.setSelected(false); + _otherButton.setSelected(true); + _javaHome.setEnabled(true); + _browse.setEnabled(true); + } + JavaHomeHandler javaHomeHandler = FrameInstaller.getJavaHomeHandler(); + if (javaHomeHandler.isValidHome()) { + _javaHome.setText(javaHomeHandler.getHome().getAbsolutePath()); + } else { + _javaHome.setText(""); + } + _javaHome.setToolTipText(_javaHome.getText()); + } + + private final class BrowseButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + JFileChooser fileChooser = new JFileChooser(new File(_javaHome.getText())); + fileChooser.setDialogTitle(getText(SELECT_JAVA_HOME)); + // the filter is at the moment only used for the title of the dialog: + fileChooser.setFileFilter(new DirectoryFilter()); + fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (fileChooser.isAcceptAllFileFilterUsed()) { + if (Installation.isMacintosh() && Installation.isJDK141()) { + // work around ArrayIndexOutOfBoundsExceptio on Mac OS X, java version 1.4.1 + } else { + fileChooser.setAcceptAllFileFilterUsed(false); + } + } + int returnValue = fileChooser.showDialog(_browse, getText(SELECT)); + if (returnValue == JFileChooser.APPROVE_OPTION) { + FrameInstaller.setJavaHomeHandler(new JavaHomeHandler(fileChooser.getSelectedFile().getAbsolutePath())); + setValues(); + } + } + } + + private final class RadioButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String actionCommand = e.getActionCommand(); + setCurrent(_CURRENT_ACTION_COMMAND.equals(actionCommand)); + } + } + + private final class JavaFocusListener implements FocusListener { + public void focusGained(FocusEvent e) { + } + + public void focusLost(FocusEvent e) { + String javaHome = _javaHome.getText(); + FrameInstaller.setJavaHomeHandler(new JavaHomeHandler(javaHome)); + _javaHome.setToolTipText(javaHome); + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/JavaSelectionPageValidator.java b/installer/src/java/org/python/util/install/JavaSelectionPageValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaSelectionPageValidator.java @@ -0,0 +1,31 @@ +package org.python.util.install; + +import org.python.util.install.Installation.JavaVersionInfo; + +public class JavaSelectionPageValidator extends AbstractWizardValidator { + + JavaSelectionPage _page; + + JavaSelectionPageValidator(JavaSelectionPage page) { + super(); + _page = page; + } + + protected void validate() throws ValidationException { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + String directory = _page.getJavaHome().getText().trim(); // trim to be sure + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(directory); + if(javaHomeHandler.isDeviation()) { + javaVersionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + if (javaVersionInfo.getErrorCode() != Installation.NORMAL_RETURN) { + throw new ValidationException(javaVersionInfo.getReason()); + } + } else { + // no experiments if current java is selected + Installation.fillJavaVersionInfo(javaVersionInfo, System.getProperties()); + } + FrameInstaller.setJavaHomeHandler(javaHomeHandler); + FrameInstaller.setJavaVersionInfo(javaVersionInfo); + } + +} diff --git a/installer/src/java/org/python/util/install/JavaVersionTester.java b/installer/src/java/org/python/util/install/JavaVersionTester.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/JavaVersionTester.java @@ -0,0 +1,61 @@ +package org.python.util.install; + +import java.io.File; + +/** + * Helper class to test a java version + */ +public class JavaVersionTester { + + public static final String JAVA_HOME = "java.home"; + protected static final String JAVA_VERSION = "java.version"; + public static final String JAVA_SPECIFICATION_VERSION = "java.specification.version"; + protected static final String JAVA_VENDOR = "java.vendor"; + private static final String NEWLINE = "\n"; + + private static final String UNKNOWN = ""; + + public static void main(String[] args) { + if (args.length > 0) { + String tempFilePath = args[0]; + File tempFile = new File(tempFilePath); + if (tempFile.exists() && tempFile.canWrite()) { + try { + writeTempFile(tempFile); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } else { + if (!tempFile.exists()) { + System.err.println("temp file " + tempFilePath + " does not exist"); + } else { + System.err.println("cannot write to temp file " + tempFilePath); + } + System.exit(1); + } + } else { + System.err.println("no temp file given. usage: JavaVersionTester tempfile"); + System.out.println("exiting with 1"); + System.exit(1); + } + } + + private static void writeTempFile(File file) throws Exception { + FileHelper.write(file, createFileContent()); + } + + private static String createFileContent() { + StringBuffer sb = new StringBuffer(500); + String java_home = new JavaHomeHandler().getExecutableName(); + if (File.separatorChar != '/') { + java_home = java_home.replace(File.separatorChar, '/'); // backslash would be interpreted as escape char + } + sb.append(JAVA_HOME + "=" + java_home + NEWLINE); + sb.append(JAVA_VERSION + "=" + System.getProperty(JAVA_VERSION, UNKNOWN) + NEWLINE); + sb.append(JAVA_SPECIFICATION_VERSION + "=" + System.getProperty(JAVA_SPECIFICATION_VERSION, UNKNOWN) + NEWLINE); + sb.append(JAVA_VENDOR + "=" + System.getProperty(JAVA_VENDOR, UNKNOWN) + NEWLINE); + return sb.toString(); + } + +} diff --git a/installer/src/java/org/python/util/install/LanguagePage.java b/installer/src/java/org/python/util/install/LanguagePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/LanguagePage.java @@ -0,0 +1,121 @@ +package org.python.util.install; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; + +public class LanguagePage extends AbstractWizardPage { + + private static Map _languageIndexes = new HashMap(2); + static { + _languageIndexes.put(Locale.ENGLISH, new Integer(0)); + _languageIndexes.put(Locale.GERMAN, new Integer(1)); + } + + private JLabel _label; + private JComboBox _languageBox; + private JarInfo _jarInfo; + private boolean _activated; + private boolean _stopListening; + + public LanguagePage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + _activated = false; + _stopListening = false; + initComponents(); + } + + private void initComponents() { + _label = new JLabel(); + add(_label); + _languageBox = new JComboBox(); + _languageBox.addActionListener(new LanguageBoxListener()); + add(_languageBox); + } + + protected String getTitle() { + return getText(WELCOME_TO_JYTHON); + } + + protected String getDescription() { + return getText(VERSION_INFO, _jarInfo.getVersion()); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _languageBox; + } + + protected void activate() { + _label.setText(getText(SELECT_LANGUAGE) + ": "); + // replace combo box items (localization) + int itemCount = _languageBox.getItemCount(); + _stopListening = true; // adding and removing fires an action event + for (int i = 0; i < itemCount; i++) { + _languageBox.removeItemAt(0); + } + _languageBox.addItem(getText(ENGLISH)); // keep indexes here + _languageBox.addItem(getText(GERMAN)); + _stopListening = false; + if (!_activated) { + // preselect German if default looks like German + if (Locale.getDefault().toString().startsWith(Locale.GERMAN.toString())) { + _languageBox.setSelectedIndex(getLanguageIndex(Locale.GERMAN)); + FrameInstaller.setLanguage(Locale.GERMAN); + } + } else { + _languageBox.setSelectedIndex(getLanguageIndex(FrameInstaller.getLanguage())); + } + _activated = true; + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private int getLanguageIndex(Locale locale) { + return ((Integer) _languageIndexes.get(locale)).intValue(); + } + + private Locale getLanguageFromIndex(int index) { + Integer indexInteger = new Integer(index); + Iterator languages = _languageIndexes.entrySet().iterator(); + while (languages.hasNext()) { + Map.Entry entry = (Map.Entry) languages.next(); + if (entry.getValue().equals(indexInteger)) { + return (Locale) entry.getKey(); + } + } + return null; + } + + private class LanguageBoxListener implements ActionListener { + public void actionPerformed(ActionEvent ae) { + if (!_stopListening) { + FrameInstaller.setLanguage(getLanguageFromIndex(_languageBox.getSelectedIndex())); + } + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/LicensePage.java b/installer/src/java/org/python/util/install/LicensePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/LicensePage.java @@ -0,0 +1,120 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.ButtonGroup; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +public class LicensePage extends AbstractWizardPage { + + private static final String _ACCEPT_ACTION_COMMAND = "1"; + private static final String _DO_NOT_ACCEPT_ACTION_COMMAND = "0"; + + private JRadioButton _acceptButton; + private JRadioButton _doNotAcceptButton; + + public LicensePage(JarInfo jarInfo) { + super(); + initComponents(jarInfo); + } + + private void initComponents(JarInfo jarInfo) { + String licenseText = "n/a"; + try { + licenseText = jarInfo.getLicenseText(); + } catch (IOException e) { + e.printStackTrace(); + } + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new GridLayout(1, 1, 10, 10)); + JTextArea textArea = new JTextArea(13, 80); + JScrollPane scrollPane = new JScrollPane(textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + textArea.setEditable(false); + textArea.setText(licenseText); + centerPanel.add(scrollPane); + + // radio buttons + JPanel southPanel = new JPanel(); + RadioButtonListener radioButtonListener = new RadioButtonListener(); + _acceptButton = new JRadioButton(); + _acceptButton.setActionCommand(_ACCEPT_ACTION_COMMAND); + _acceptButton.addActionListener(radioButtonListener); + _doNotAcceptButton = new JRadioButton(); + _doNotAcceptButton.setActionCommand(_DO_NOT_ACCEPT_ACTION_COMMAND); + _doNotAcceptButton.addActionListener(radioButtonListener); + ButtonGroup radioButtonGroup = new ButtonGroup(); + radioButtonGroup.add(_acceptButton); + radioButtonGroup.add(_doNotAcceptButton); + JPanel radioPanel = new JPanel(new GridLayout(1, 0)); + radioPanel.add(_acceptButton); + radioPanel.add(_doNotAcceptButton); + southPanel.add(radioPanel); + + setLayout(new BorderLayout(0, 5)); + add(centerPanel, BorderLayout.CENTER); + add(southPanel, BorderLayout.SOUTH); + } + + protected String getTitle() { + return getText(LICENSE); + } + + protected String getDescription() { + return getText(PLEASE_READ_LICENSE); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return _doNotAcceptButton; + } + + protected void activate() { + _acceptButton.setText(getText(ACCEPT)); + _doNotAcceptButton.setText(getText(DO_NOT_ACCEPT)); + boolean accept = FrameInstaller.isAccept(); + _acceptButton.setSelected(accept); + _doNotAcceptButton.setSelected(!accept); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + protected boolean isAccept() { + return _acceptButton.isSelected() && !_doNotAcceptButton.isSelected(); + } + + private final static class RadioButtonListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + String actionCommand = e.getActionCommand(); + if (actionCommand.equals(_ACCEPT_ACTION_COMMAND)) { + FrameInstaller.setAccept(true); + } else { + FrameInstaller.setAccept(false); + } + } + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/LicensePageValidator.java b/installer/src/java/org/python/util/install/LicensePageValidator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/LicensePageValidator.java @@ -0,0 +1,17 @@ +package org.python.util.install; + +public class LicensePageValidator extends AbstractWizardValidator { + + LicensePage _page; + + LicensePageValidator(LicensePage page) { + super(); + _page = page; + } + + protected void validate() throws ValidationException { + if (!_page.isAccept()) { + throw new ValidationException(getText(PLEASE_ACCEPT_LICENSE)); + } + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/OverviewPage.java b/installer/src/java/org/python/util/install/OverviewPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/OverviewPage.java @@ -0,0 +1,243 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; + +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import org.python.util.install.Installation.JavaVersionInfo; + +public class OverviewPage extends AbstractWizardPage { + + private final static int _LONGER_LENGTH = 25; + private final static int _SHORTER_LENGTH = 10; + + private JLabel _directoryLabel; + private JLabel _typeLabel; + private JTextField _directory; + private JTextField _type; + private JLabel _message; + + private JLabel _osLabel; + private JCheckBox _osBox; + private JLabel _javaLabel; + private JTextField _javaVendor; + private JTextField _javaVersion; + private JCheckBox _javaBox; + + public OverviewPage() { + super(); + initComponents(); + } + + private void initComponents() { + _directoryLabel = new JLabel(); + _directory = new JTextField(_LONGER_LENGTH); + _directory.setEditable(false); + _directory.setFocusable(false); + _typeLabel = new JLabel(); + _type = new JTextField(_LONGER_LENGTH); + _type.setEditable(false); + _type.setFocusable(false); + + _osLabel = new JLabel(); + JTextField osName = new JTextField(_LONGER_LENGTH); + osName.setText(System.getProperty(Installation.OS_NAME)); + osName.setToolTipText(System.getProperty(Installation.OS_NAME)); + osName.setEditable(false); + osName.setFocusable(false); + JTextField osVersion = new JTextField(_SHORTER_LENGTH); + osVersion.setText(System.getProperty(Installation.OS_VERSION)); + osVersion.setToolTipText(System.getProperty(Installation.OS_VERSION)); + osVersion.setEditable(false); + osVersion.setFocusable(false); + _osBox = new JCheckBox(); + _osBox.setEnabled(false); + _osBox.setSelected(Installation.isValidOs()); + _osBox.setFocusable(false); + + _javaLabel = new JLabel(); + _javaLabel.setFocusable(false); + _javaVendor = new JTextField(_LONGER_LENGTH); + _javaVendor.setEditable(false); + _javaVendor.setFocusable(false); + _javaVersion = new JTextField(_SHORTER_LENGTH); + _javaVersion.setEditable(false); + _javaVersion.setFocusable(false); + _javaBox = new JCheckBox(); + _javaBox.setEnabled(false); + _javaBox.setFocusable(false); + + _message = new JLabel(); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + panel.add(_directoryLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + panel.add(_directory, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(_typeLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panel.add(_type, gridBagConstraints); + + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + panel.add(_osLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 2; + panel.add(osName, gridBagConstraints); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 2; + panel.add(osVersion, gridBagConstraints); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 2; + panel.add(_osBox, gridBagConstraints); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 3; + panel.add(_javaLabel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 3; + panel.add(_javaVendor, gridBagConstraints); + gridBagConstraints.gridx = 2; + gridBagConstraints.gridy = 3; + panel.add(_javaVersion, gridBagConstraints); + gridBagConstraints.gridx = 3; + gridBagConstraints.gridy = 3; + panel.add(_javaBox, gridBagConstraints); + + // attn special constraints for message (should always be on last row) + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 4; + gridBagConstraints.gridwidth = 2; + panel.add(_message, gridBagConstraints); + + add(panel); + } + + protected String getTitle() { + return getText(OVERVIEW_TITLE); + } + + protected String getDescription() { + return getText(OVERVIEW_DESCRIPTION); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + // directory + _directoryLabel.setText(getText(TARGET_DIRECTORY_PROPERTY) + ": "); + _directory.setText(FrameInstaller.getTargetDirectory()); + _directory.setToolTipText(FrameInstaller.getTargetDirectory()); + + // type + _typeLabel.setText(getText(INSTALLATION_TYPE) + ": "); + InstallationType installationType = FrameInstaller.getInstallationType(); + String typeText; + if (installationType.isAll()) { + typeText = getText(ALL); + } else if (installationType.isStandard()) { + typeText = getText(STANDARD); + } else if (installationType.isMinimum()) { + typeText = getText(MINIMUM); + } else if (installationType.isStandalone()) { + typeText = getText(STANDALONE); + } else { + typeText = getText(CUSTOM); + typeText += " ("; + boolean predecessor = false; + if (installationType.installLibraryModules()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES; + predecessor = true; + } + if (installationType.installDemosAndExamples()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES; + predecessor = true; + } + if (installationType.installDocumentation()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_DOCUMENTATION; + predecessor = true; + } + if (installationType.installSources()) { + if (predecessor) { + typeText += " "; + } + typeText += InstallerCommandLine.INEXCLUDE_SOURCES; + predecessor = true; + } + typeText += ")"; + } + _type.setText(typeText); + _type.setToolTipText(typeText); + + // os + _osLabel.setText(getText(OS_INFO) + ": "); + String osText; + if (_osBox.isSelected()) { + osText = getText(OK); + } else { + osText = getText(MAYBE_NOT_SUPPORTED); + } + _osBox.setText(osText); + + // java + _javaLabel.setText(getText(JAVA_INFO) + ": "); + JavaVersionInfo javaVersionInfo = FrameInstaller.getJavaVersionInfo(); + _javaVendor.setText(javaVersionInfo.getVendor()); + _javaVendor.setToolTipText(javaVersionInfo.getVendor()); + _javaVersion.setText(javaVersionInfo.getVersion()); + _javaVersion.setToolTipText(javaVersionInfo.getVersion()); + _javaBox.setSelected(Installation.isValidJava(javaVersionInfo)); + String javaText; + if (_javaBox.isSelected()) { + javaText = getText(OK); + } else { + javaText = getText(NOT_OK); + } + _javaBox.setText(javaText); + + // message + _message.setText(getText(CONFIRM_START, getText(NEXT))); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ProgressListener.java b/installer/src/java/org/python/util/install/ProgressListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ProgressListener.java @@ -0,0 +1,15 @@ +package org.python.util.install; + +public interface ProgressListener extends InstallationListener { + + public int getInterval(); + + public void progressChanged(int newPercentage); + + public void progressEntry(String entry); + + public void progressStartScripts(); + + public void progressStandalone(); + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ProgressPage.java b/installer/src/java/org/python/util/install/ProgressPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ProgressPage.java @@ -0,0 +1,122 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.io.File; +import java.io.IOException; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; + +import org.python.util.install.driver.Autotest; + +public class ProgressPage extends AbstractWizardPage implements ProgressListener { + + private static final long serialVersionUID = 9013748834030994976L; + + private JarInfo _jarInfo; + private JLabel _label; + private JProgressBar _progressBar; + private JLabel _progressEntry; + private Autotest _autotest; + + public ProgressPage(JarInfo jarInfo, Autotest autotest) { + super(); + _jarInfo = jarInfo; + _autotest = autotest; + initComponents(); + } + + private void initComponents() { + JPanel northPanel = new JPanel(); + _label = new JLabel(); + northPanel.add(_label); + _progressBar = new JProgressBar(); + northPanel.add(_progressBar); + JPanel centerPanel = new JPanel(); + _progressEntry = new JLabel(); + centerPanel.add(_progressEntry); + setLayout(new BorderLayout(0, 5)); + add(northPanel, BorderLayout.NORTH); + add(centerPanel, BorderLayout.CENTER); + } + + protected String getTitle() { + return getText(INSTALLATION_IN_PROGRESS); + } + + protected String getDescription() { + return getText(PLEASE_WAIT); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return false; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + _label.setText(getText(PROGRESS) + ": "); + _progressBar.setValue(0); + _progressBar.setStringPainted(true); + try { + _progressEntry.setText(getText(INFLATING, _jarInfo.getJarFile().getName())); + } catch (IOException e) { + // should not happen + } + JarInstaller jarInstaller = new JarInstaller(this, _jarInfo); + if (_autotest != null) { + jarInstaller.addInstallationListener(_autotest); + } + File targetDirectory = new File(FrameInstaller.getTargetDirectory()); + JavaHomeHandler javaHomeHandler = FrameInstaller.getJavaHomeHandler(); + jarInstaller.inflate(targetDirectory, FrameInstaller.getInstallationType(), javaHomeHandler); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + // + // interface ProgressListener + // + + public void progressChanged(int newPercentage) { + _progressBar.setValue(newPercentage); + } + + public int getInterval() { + return 1; + } + + public void progressFinished() { + _progressBar.setValue(100); + getWizard().gotoNextPage(); + } + + public void progressEntry(String entry) { + _progressEntry.setText(getText(INFLATING, entry)); + } + + public void progressStartScripts() { + _progressEntry.setText(getText(GENERATING_START_SCRIPTS)); + } + + public void progressStandalone() { + _progressEntry.setText(getText(PACKING_STANDALONE_JAR)); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ReadmePage.java b/installer/src/java/org/python/util/install/ReadmePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ReadmePage.java @@ -0,0 +1,75 @@ +package org.python.util.install; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.io.IOException; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +public class ReadmePage extends AbstractWizardPage { + + private JTextArea _textArea; + private JarInfo _jarInfo; + + public ReadmePage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + initComponents(); + } + + private void initComponents() { + JPanel centerPanel = new JPanel(); + centerPanel.setLayout(new GridLayout(1, 1)); + _textArea = new JTextArea(13, 80); + JScrollPane scrollPane = new JScrollPane(_textArea, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, + JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); + _textArea.setEditable(false); + _textArea.setText("n/a"); + centerPanel.add(scrollPane); + + setLayout(new BorderLayout(0, 5)); + add(centerPanel, BorderLayout.CENTER); + } + + protected String getTitle() { + return getText(README); + } + + protected String getDescription() { + return getText(PLEASE_README); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + try { + _textArea.setText(_jarInfo.getReadmeText()); + } catch (IOException ioe) { + throw new InstallerException(ioe); + } + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/StandalonePackager.java b/installer/src/java/org/python/util/install/StandalonePackager.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/StandalonePackager.java @@ -0,0 +1,184 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +public class StandalonePackager { + + private final static int BUF_SIZE = 1024; + + private File _jarFile; + private Manifest _manifest; + private JarOutputStream _jarOut; + + /** + * Helper class to pack stuff into a single .jar file. + *

+ * If a .jar file has to be added, this should be the first add (because of the MANIFEST). + */ + public StandalonePackager(File jarFile) { + _jarFile = jarFile; + } + + /** + * add a file, in given parent dir (null = top) + * + * @param file to write to jar + * @param parentDir as String + * + * @throws IOException + */ + public void addFile(File file, String parentDir) throws IOException { + byte[] buffer = new byte[BUF_SIZE]; + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + String jarEntryName = null; + if (parentDir != null && parentDir.length() > 0) { + jarEntryName = parentDir + "/" + file.getName(); + } else { + jarEntryName = file.getName(); + } + getJarOutputStream().putNextEntry(new JarEntry(jarEntryName)); + for (int read = 0; read != -1; read = inputStream.read(buffer)) { + getJarOutputStream().write(buffer, 0, read); + } + getJarOutputStream().closeEntry(); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + + /** + * add a full directory + * + * @param directory + * @throws IOException + */ + public void addFullDirectory(File directory) throws IOException { + addDirectory(directory, null); + } + + /** + * add the contents of a given jar file + * + * @param jarFile to add + * @throws IOException + * @throws FileNotFoundException + */ + public void addJarFile(File jarFile) throws FileNotFoundException, IOException { + JarFile jarJarFile = new JarFile(jarFile); + try { + _manifest = jarJarFile.getManifest(); + } finally { + jarJarFile.close(); + } + + JarInputStream inputStr = null; + try { + inputStr = new JarInputStream(new FileInputStream(jarFile)); + JarEntry entry = inputStr.getNextJarEntry(); + while (entry != null) { + getJarOutputStream().putNextEntry(entry); + byte[] buffer = new byte[BUF_SIZE]; + int len; + while ((len = inputStr.read(buffer)) > 0) { + getJarOutputStream().write(buffer, 0, len); + } + getJarOutputStream().closeEntry(); + entry = inputStr.getNextJarEntry(); + } + } finally { + if (inputStr != null) { + try { + inputStr.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public void close() throws FileNotFoundException, IOException { + getJarOutputStream().close(); + } + + /** + * removes all files in directory dir (and subdirectories), except excludeFile. + * + * @param dir + * @param excludeFile + */ + public static void emptyDirectory(File dir, File excludeFile) { + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + if (!files[i].equals(excludeFile)) { + if (files[i].isDirectory()) { + emptyDirectory(files[i], excludeFile); + } else { + files[i].delete(); + } + } + } + if (dir.listFiles().length == 0) { + dir.delete(); + } + } + + /** + * add the given directory to the jar, including all subdirectories, in given parent dir (null = + * top) + * + * @param dir to add to the jar + * @param parentDir to save in jar + * @throws IOException + */ + private void addDirectory(File dir, String parentDir) throws IOException { + if (!dir.isDirectory()) + return; + File[] filesInDir = dir.listFiles(); + for (int i = 0; i < filesInDir.length; i++) { + File currentFile = filesInDir[i]; + if (currentFile.isFile()) { + if (parentDir != null && parentDir.length() > 0) { + addFile(currentFile, parentDir + "/" + dir.getName()); + } else { + addFile(currentFile, dir.getName()); + } + } else { + String newParentDir = null; + if (parentDir != null && parentDir.length() > 0) { + newParentDir = parentDir + "/" + dir.getName(); + } else { + newParentDir = dir.getName(); + } + addDirectory(currentFile, newParentDir); + } + } + } + + /** + * @return the jar output stream + */ + private JarOutputStream getJarOutputStream() throws FileNotFoundException, IOException { + if (_jarOut == null) { + if (_manifest != null) { + _jarOut = new JarOutputStream(new FileOutputStream(_jarFile), _manifest); + } else { + _jarOut = new JarOutputStream(new FileOutputStream(_jarFile)); + } + } + return _jarOut; + } + +} diff --git a/installer/src/java/org/python/util/install/StartScriptGenerator.java b/installer/src/java/org/python/util/install/StartScriptGenerator.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/StartScriptGenerator.java @@ -0,0 +1,244 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Date; + +public class StartScriptGenerator { + + protected final static int UNIX_FLAVOUR = 10; + + protected final static int WINDOWS_FLAVOUR = 30; + + protected final static int BOTH_FLAVOUR = 50; + + protected final static String WIN_CR_LF; + + private final static String JAVA_HOME = "JAVA_HOME"; + + /** do not hard-wire JYTHON_HOME */ + private final static String JYTHON_HOME_FALLBACK = "JYTHON_HOME_FALLBACK"; + + private final static String JYTHON = "jython"; + + private final static String JYTHON_BAT = "jython.bat"; + static { + int dInt = Integer.parseInt("0d", 16); + int aInt = Integer.parseInt("0a", 16); + WIN_CR_LF = new String(new char[] {(char)dInt, (char)aInt}); + } + + private File _targetDirectory; + + private JavaHomeHandler _javaHomeHandler; + + private int _flavour; + + public StartScriptGenerator(File targetDirectory, JavaHomeHandler javaHomeHandler) { + _targetDirectory = targetDirectory; + _javaHomeHandler = javaHomeHandler; + if (Installation.isWindows()) { + setFlavour(WINDOWS_FLAVOUR); + } else { + // everything else defaults to unix at the moment + setFlavour(UNIX_FLAVOUR); + } + } + + protected void setFlavour(int flavour) { + _flavour = flavour; + if (flavour == WINDOWS_FLAVOUR) { + // check if we should create unix like scripts, too + if (hasUnixlikeShell()) { + _flavour = BOTH_FLAVOUR; + } + } + } + + protected int getFlavour() { + return _flavour; + } + + protected boolean hasUnixlikeShell() { + int errorCode = 0; + try { + String command[] = new String[] {"sh", "-c", "env"}; + long timeout = 3000; + ChildProcess childProcess = new ChildProcess(command, timeout); + childProcess.setDebug(false); + childProcess.setSilent(true); + errorCode = childProcess.run(); + } catch (Throwable t) { + errorCode = 1; + } + return errorCode == 0; + } + + protected final void generateStartScripts() throws IOException { + File bin = new File(getTargetDirectory(), "bin"); + File bin_jython = new File(bin, JYTHON); + switch(getFlavour()){ + case BOTH_FLAVOUR: + writeToTargetDir(JYTHON_BAT, getJythonScript(WINDOWS_FLAVOUR)); + FileHelper.makeExecutable(writeToTargetDir(JYTHON, getJythonScript(BOTH_FLAVOUR))); + FileHelper.makeExecutable(bin_jython); + break; + case WINDOWS_FLAVOUR: + writeToTargetDir(JYTHON_BAT, getJythonScript(WINDOWS_FLAVOUR)); + // delete the *nix script in /bin dir + bin_jython.delete(); + break; + default: + FileHelper.makeExecutable(writeToTargetDir(JYTHON, getJythonScript(UNIX_FLAVOUR))); + FileHelper.makeExecutable(bin_jython); + // delete the windows script in /bin dir + File bin_jython_bat = new File(bin, JYTHON_BAT); + bin_jython_bat.delete(); + break; + } + } + + /** + * only protected for unit test use + */ + protected final String getJythonScript(int flavour) throws IOException { + if (flavour == WINDOWS_FLAVOUR) { + return getStartScript(getWindowsJythonTemplate()) + readFromFile(JYTHON_BAT); + } else { + return getStartScript(getUnixJythonTemplate()) + readFromFile(JYTHON); + } + } + + /** + * These placeholders are valid for all private methods: + * + * {0} : current date
+ * {1} : user.name
+ * {2} : target directory
+ */ + private String getStartScript(String template) throws IOException { + String parameters[] = new String[4]; + parameters[0] = new Date().toString(); + parameters[1] = System.getProperty("user.name"); + parameters[2] = getTargetDirectory().getCanonicalPath(); + return MessageFormat.format(template, (Object[])parameters); + } + + /** + * placeholders: + * + * @see getStartScript + */ + private String getWindowsJythonTemplate() { + StringBuilder builder = getWindowsHeaderTemplate(); + builder.append("set "); + builder.append(JAVA_HOME); + builder.append("="); + if (_javaHomeHandler.isValidHome()) { + builder.append("\""); + builder.append(_javaHomeHandler.getHome().getAbsolutePath()); + builder.append("\""); + } + builder.append(WIN_CR_LF); + builder.append("set "); + builder.append(JYTHON_HOME_FALLBACK); + builder.append("=\"{2}\""); + builder.append(WIN_CR_LF); + builder.append(WIN_CR_LF); + return builder.toString(); + } + + /** + * placeholders: + * + * @see getStartScript + */ + private StringBuilder getWindowsHeaderTemplate() { + StringBuilder builder = new StringBuilder(1000); + builder.append("@echo off"); + builder.append(WIN_CR_LF); + builder.append("rem Prevent leak of environment variables"); + builder.append(WIN_CR_LF); + builder.append("setlocal"); + builder.append(WIN_CR_LF); + builder.append("rem This file was generated by the Jython installer"); + builder.append(WIN_CR_LF); + builder.append("rem Created on {0} by {1}"); + builder.append(WIN_CR_LF); + builder.append(WIN_CR_LF); + return builder; + } + + /** + * placeholders: + * + * @see getStartScript + */ + private String getUnixJythonTemplate() { + StringBuilder builder = getUnixHeaderTemplate(); + builder.append(JAVA_HOME); + builder.append("="); + if (_javaHomeHandler.isValidHome()) { + builder.append("\""); + builder.append(_javaHomeHandler.getHome().getAbsolutePath()); + builder.append("\""); + } + builder.append("\n"); + builder.append(JYTHON_HOME_FALLBACK); + builder.append("=\"{2}\"\n"); + builder.append("\n"); + return builder.toString(); + } + + /** + * placeholders: + * + * @see getStartScript + */ + private StringBuilder getUnixHeaderTemplate() { + StringBuilder builder = new StringBuilder(1000); + builder.append("#!/usr/bin/env bash\n"); + builder.append("\n"); + builder.append("# This file was generated by the Jython installer\n"); + builder.append("# Created on {0} by {1}\n"); + builder.append("\n"); + return builder; + } + + /** + * @param fileName + * The short file name, e.g. JYTHON_BAT + * + * @throws IOException + */ + private String readFromFile(String fileName) throws IOException { + // default runtime location + File targetDirectory = getTargetDirectory(); + File file = new File(new File(targetDirectory, "bin"), fileName); + if (!file.exists()) { + // deviation: test time location + file = new File(targetDirectory, fileName); + } + return FileHelper.readAll(file); + } + + /** + * Create (or overwrite) the specified file in the target directory + * + * @param fileName + * The short file name, e.g. JYTHON_BAT + * @param contents + * + * @throws IOException + */ + private File writeToTargetDir(String fileName, String contents) throws IOException { + File file = new File(getTargetDirectory(), fileName); + FileHelper.write(file, contents); + return file; + } + + private File getTargetDirectory() { + return _targetDirectory; + } +} diff --git a/installer/src/java/org/python/util/install/SuccessPage.java b/installer/src/java/org/python/util/install/SuccessPage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/SuccessPage.java @@ -0,0 +1,55 @@ +package org.python.util.install; + +import javax.swing.JComponent; +import javax.swing.JLabel; + +public class SuccessPage extends AbstractWizardPage { + + private JarInfo _jarInfo; + private JLabel _label; + + public SuccessPage(JarInfo jarInfo) { + super(); + _jarInfo = jarInfo; + initComponents(); + } + + private void initComponents() { + _label = new JLabel(); + add(_label); + } + + protected String getTitle() { + return getText(CONGRATULATIONS); + } + + protected String getDescription() { + return getText(SUCCESS, _jarInfo.getVersion(), FrameInstaller.getTargetDirectory()); + } + + protected boolean isCancelVisible() { + return false; + } + + protected boolean isPreviousVisible() { + return false; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + return null; + } + + protected void activate() { + _label.setText(getText(PRESS_FINISH, getWizard().getFinishString())); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextConstants.java b/installer/src/java/org/python/util/install/TextConstants.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextConstants.java @@ -0,0 +1,148 @@ +package org.python.util.install; + +import java.util.ListResourceBundle; + +public class TextConstants extends ListResourceBundle implements TextKeys { + + static final Object[][] contents = { + // LOCALIZE THIS + + { ACCEPT, "I accept" }, // license + { ALL, "All (everything, including sources)" }, // installation type + { BROWSE, "Browse..." }, // button (open the JFileChooser) + { CANCEL, "Cancel" }, // button + { CHOOSE_LOCATION, "Choose the location where you want Jython to be installed to" }, // selection + { CHOOSE_JRE, "Choose the java version (JRE/JDK) to run Jython with" }, // selection + { CONFIRM_START, "Please press {0} to start the installation" }, // overview + { CONGRATULATIONS, "Congratulations!" }, // congratulations + { CORE, "Core" }, // installation type + { CREATED_DIRECTORY, "Created directory {0}" }, // directory + { CURRENT, "Current" }, // directory + { CUSTOM, "Custom" }, // installation type + { DEMOS_EXAMPLES, "Demos and examples" }, // installation type + { DO_NOT_ACCEPT, "I do not accept" }, // license + { DIRECTORIES_ONLY, "Directories only" }, // file chooser + { DOCUMENTATION, "Documentation" }, // installation type + { EMPTY_TARGET_DIRECTORY, "Target directory must not be empty" }, // error + { ENGLISH, "English" }, // language + { ERROR, "Error" }, // error + { ERROR_ACCESS_JARFILE, "Error accessing jar file" }, // error + { FINISH, "Finish" }, // button + { GENERATING_START_SCRIPTS, "Generating start scripts ..." }, // progress + { GERMAN, "German" }, // language + { INFLATING, "Inflating {0}" }, // progress + { INFORMATION, "Information" }, // information + { INSTALLATION_CANCELLED, "Installation cancelled." }, // final + { INSTALLATION_IN_PROGRESS, "The installation is now in progress" }, // progress + { INSTALLATION_TYPE_DESCRIPTION, "The following installation types are available" }, // installation type + { INSTALLATION_TYPE, "Installation type" }, // installation type + { JAR_NOT_FOUND, "Unable to find jar file {0}." }, // error + { JAVA_INFO, "Java vendor / version" }, // version + { JYTHON_INSTALL, "Jython Installation" }, // title + { LANGUAGE_PROPERTY, "Language" }, // language + { LIBRARY_MODULES, "Library modules" }, // installation type + { LICENSE, "License agreement" }, // license + { MAYBE_NOT_SUPPORTED, "Maybe not supported" }, // version + { MINIMUM, "Minimum (core)" }, // installation type + { NEXT, "Next" }, // button + { NON_EMPTY_TARGET_DIRECTORY, "Target directory is not empty" }, // error + { NO_MANIFEST, "No manifest found in jar file {0}." }, // error + { NOT_OK, "Not ok !" }, // version + { OK, "Ok" }, // version + { OS_INFO, "OS name / version" }, // version + { OTHER, "Other" }, // directory + { OVERVIEW_DESCRIPTION, "The installation will be done using the following options" }, // overview + { OVERVIEW_TITLE, "Overview (summary of options)" }, // overview + { PACKING_STANDALONE_JAR, "Packing standalone " + JarInstaller.JYTHON_JAR + " ..." }, // progress + { PLEASE_ACCEPT_LICENSE, "Please read and accept the license agreement" }, // license + { PLEASE_README, "Please read the following information" }, // readme + { PLEASE_READ_LICENSE, "Please read the license agreement carefully" }, // license + { PLEASE_WAIT, "Please stand by, this may take a few seconds ..." }, // progress + { PRESS_FINISH, "Please press {0} to exit the installation." }, // finish + { PREVIOUS, "Previous" }, // button + { PROGRESS, "Progress" }, // progress + { README, "README" }, // readme + { SELECT, "Select" }, // button (approval in JFileChooser) + { SELECT_INSTALLATION_TYPE, "Please select the installation type" }, // installation type + { SELECT_JAVA_HOME, "Please select the java home directory" }, // directory + { SELECT_LANGUAGE, "Please select your language" }, // language + { SELECT_TARGET_DIRECTORY, "Please select the target directory" }, // directory + { SOURCES, "Sources" }, // installation type + { STANDARD, "Standard (core, library modules, demos, examples, documentation)" }, // installation type + { STANDALONE, "Standalone (a callable .jar file)" }, // installation type + { SUCCESS, "You successfully installed Jython {0} to directory {1}." }, // success + { TARGET_DIRECTORY_PROPERTY, "Target directory" }, // property as title + { TARGET_JAVA_HOME_PROPERTY, "Target java home" }, // property as title + { UNABLE_CREATE_DIRECTORY, "Unable to create directory {0}." }, // error + { UNABLE_CREATE_FILE, "Unable to create file {0}." }, // error + { UNABLE_TO_DELETE, "Unable to delete {0}" }, // error + { UNEXPECTED_URL, "Unexpected URL {0} found for installation jar file." }, // error + { VERSION_INFO, "You are about to install Jython version {0}" }, // version + { WELCOME_TO_JYTHON, "Welcome to Jython !" }, // welcome + { ZIP_ENTRY_SIZE, "Size of zip entry {0} unknown." }, // error + { ZIP_ENTRY_TOO_BIG, "Zip entry {0} too big." }, // error + + // console texts (C_*) should not contain special characters (like e.g. ü) + { C_ACCEPT, "Do you accept the license agreement ?" }, // license + { C_ALL, "All (everything, including sources)" }, // installation type + { C_AT_ANY_TIME_CANCEL, "(at any time, answer {0} to cancel the installation)" }, // console + { C_AVAILABLE_LANGUAGES, "For the installation process, the following languages are available: {0}" }, // console + { C_CHECK_JAVA_VERSION, "Checking java version ..." }, // progress + { C_CLEAR_DIRECTORY, "Contents of directory {0} will be deleted now! Are you sure to proceed ?" }, //console + { C_CONFIRM_TARGET, "Please confirm copying of files to directory {0}" }, // console + { C_CONGRATULATIONS, "Congratulations!" }, // congratulations + { C_CREATE_DIRECTORY, "Unable to find directory {0}, create it ?" }, // console + { C_ENTER_TARGET_DIRECTORY, "Please enter the target directory" }, // console + { C_ENTER_JAVA_HOME, "Please enter the java home directory (empty for using the current java runtime)" }, // console + { C_ENGLISH, "English" }, // language + { C_EXCLUDE, "Do you want to exclude parts from the installation ?" }, // installation type + { C_GENERATING_START_SCRIPTS, "Generating start scripts ..." }, // progress + { C_GERMAN, "German" }, // language + { C_INCLUDE, "Do you want to install additional parts ?" }, // installation type + { C_INEXCLUDE_PARTS, "The following parts are selectable ({0} = no more)" }, // installation type + { C_INSTALL_TYPES, "The following installation types are available:" }, // installation type + { C_INVALID_ANSWER, "Answer {0} is not valid here" }, // error + { C_JAVA_VERSION, "Your java version to start Jython is: {0} / {1}" }, // version + { C_MINIMUM, "Minimum (core)" }, // installation type + { C_NO, "n" }, // answer + { C_NO_BIN_DIRECTORY, "There is no /bin directory below {0}." }, // error + { C_NO_JAVA_EXECUTABLE, "No java executable found in {0}." }, // error + { C_NO_VALID_JAVA, "No valid java found in {0}." }, // error + { C_NON_EMPTY_TARGET_DIRECTORY, "Target directory {0} is not empty" }, // error + { C_NOT_A_DIRECTORY, "{0} is not a directory. " }, // error + { C_NOT_FOUND, "{0} not found. " }, // error + { C_OS_VERSION, "Your operating system version is: {0} / {1}" }, // version + { C_OVERWRITE_DIRECTORY, "Directory {0} is not empty - ok to overwrite contents ?" }, // console + { C_PACKING_STANDALONE_JAR, "Packing standalone " + JarInstaller.JYTHON_JAR + " ..." }, // progress + { C_PROCEED, "Please press Enter to proceed" }, // console + { C_PROCEED_ANYWAY, "Please press Enter to proceed anyway" }, // console + { C_READ_LICENSE, "Do you want to read the license agreement now ?" }, // license + { C_READ_README, "Do you want to show the contents of README ?" }, // readme + { C_SCHEDULED, "{0} scheduled for installation" }, // installation type + { C_SELECT_INSTALL_TYPE, "Please select the installation type" }, // installation type + { C_SELECT_LANGUAGE, "Please select your language" }, // language + { C_SILENT_INSTALLATION, "Performing silent installation" }, // installation mode + { C_STANDALONE, "Standalone (a single, executable .jar)" }, //installation mode + { C_STANDARD, "Standard (core, library modules, demos and examples, documentation)" }, // installation type + { C_SUCCESS, "You successfully installed Jython {0} to directory {1}." }, // success + { C_SUMMARY, "Summary:" }, // summary + { C_TO_CURRENT_JAVA, "Warning: switching back to current JDK due to error: {0}." }, // warning + { C_UNABLE_CREATE_DIRECTORY, "Unable to create directory {0}." }, // error + { C_UNABLE_CREATE_TMPFILE, "Unable to create temp file {0}." }, // error + { C_UNSCHEDULED, "{0} excluded from installation" }, // installation type + { C_UNABLE_TO_DELETE, "Unable to delete {0}" }, // error + { C_UNSUPPORTED_JAVA, "This java version is not supported." }, // version + { C_UNSUPPORTED_OS, "This operating system might not be fully supported." }, // version + { C_USING_TYPE, "Using installation type {0}" }, // installation type + { C_VERSION_INFO, "You are about to install Jython version {0}" }, // version + { C_WELCOME_TO_JYTHON, "Welcome to Jython !" }, // welcome + { C_YES, "y" }, // answer + + // END OF MATERIAL TO LOCALIZE + }; + + public Object[][] getContents() { + return contents; + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextConstants_de.java b/installer/src/java/org/python/util/install/TextConstants_de.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextConstants_de.java @@ -0,0 +1,148 @@ +package org.python.util.install; + +import java.util.ListResourceBundle; + +public class TextConstants_de extends ListResourceBundle implements TextKeys, UnicodeSequences { + + static final Object[][] contents = { + // Die folgenden Texte duerfen Umlaute und Sonderzeichen enthalten, aber nur als Unicode Escape Sequenzen aus UnicodeSequences + // The following texts may contain special characters, but only as unicode escape sequences from UnicodeSequences + { ACCEPT, "Ja, ich akzeptiere" }, // license + { ALL, "Alles (volle Installation, inklusive Quellcode)" }, // installation type + { BROWSE, "Suchen..." }, // button (open the JFileChooser) + { CANCEL, "Abbrechen" }, // button text + { CHOOSE_LOCATION, "W"+a2+"hlen Sie das Verzeichnis, in das Jython installiert werden soll" }, // selection + { CHOOSE_JRE, "Bestimmen Sie die Java Version (JRE/JDK), mit welcher Jython gestartet werden soll" }, // selection + { CONFIRM_START, "Bitte dr"+u2+"cken Sie {0}, um die Installation zu starten" }, // overview + { CONGRATULATIONS, "Gratulation!" }, // congratulations + { CORE, "Kern" }, // installation type + { CREATED_DIRECTORY, "Verzeichnis {0} wurde erstellt" }, // directory + { CURRENT, "Das aktuelle" }, // directory + { CUSTOM, "Benutzerdefiniert" }, // installation type + { DEMOS_EXAMPLES, "Demos und Beispiele" }, // installation type + { DIRECTORIES_ONLY, "Nur Verzeichnisse" }, // file chooser + { DOCUMENTATION, "Dokumentation" }, // installation type + { DO_NOT_ACCEPT, "Nein, ich akzeptiere nicht" }, // license + { EMPTY_TARGET_DIRECTORY, "Das Zielverzeichnis darf nicht leer sein" }, // error + { ENGLISH, "Englisch" }, // language + { ERROR, "Fehler" }, // error + { ERROR_ACCESS_JARFILE, "Problem beim Zugriff auf das jar File" }, // error + { FINISH, "Beenden" }, // button + { GENERATING_START_SCRIPTS, "Start Scripts werden generiert ..." }, // progress + { GERMAN, "Deutsch" }, // language + { INFLATING, "Entpacke {0}" }, // progress + { INFORMATION, "Information" }, // information + { INSTALLATION_CANCELLED, "Sie haben die Installation abgebrochen." }, // final + { INSTALLATION_IN_PROGRESS, "Die Installation l"+a2+"uft" }, // progress + { INSTALLATION_TYPE_DESCRIPTION, "Die folgenden Installationstypen sind verf"+u2+"gbar" }, // installation type + { INSTALLATION_TYPE, "Installationstyp" }, // installation type + { JAVA_INFO, "Java Hersteller / Version" }, // version + { JAR_NOT_FOUND, "Jar File {0} nicht gefunden." }, // error + { JYTHON_INSTALL, "Jython Installation" }, // title + { LANGUAGE_PROPERTY, "Sprache" }, // language + { LIBRARY_MODULES, "Bibliotheksmodule" }, // installation type + { LICENSE, "Lizenzvereinbarung" }, // license + { MAYBE_NOT_SUPPORTED, "Eventuell nicht unterst"+u2+"tzt" }, // version + { MINIMUM, "Minimum (Kern)" }, // installation type + { NEXT, "Weiter" }, // button + { NON_EMPTY_TARGET_DIRECTORY, "Das Zielverzeichnis enth"+a2+"lt bereits Daten." }, // error + { NO_MANIFEST, "Jar File {0} enth"+a2+"lt kein Manifest." }, // error + { NOT_OK, "Nicht ok !" }, // version + { OK, "Ok" }, // version + { OS_INFO, "Betriebssystem / Version" }, // version + { OTHER, "Ein abweichendes" }, // directory + { OVERVIEW_DESCRIPTION, "Sie haben folgende Einstellungen f"+u2+"r die Installation ausgew"+a2+"hlt" }, // overview + { OVERVIEW_TITLE, U2+"bersicht "+u2+"ber die gew"+a2+"hlten Einstellungen" }, // overview + { PACKING_STANDALONE_JAR, "Das standalone " + JarInstaller.JYTHON_JAR + " File wird erstellt ..." }, // progress + { PLEASE_ACCEPT_LICENSE, "Bitte lesen und akzeptieren Sie die Lizenzvereinbarung" }, // license + { PLEASE_README, "Bitte lesen Sie die folgenden Informationen" }, // readme + { PLEASE_READ_LICENSE, "Bitte lesen Sie die Lizenzvereinbarung sorf"+a2+"ltig durch" }, // license + { PLEASE_WAIT, "Bitte um etwas Geduld, die Installation kann einige Sekunden dauern ..." }, // progress + { PRESS_FINISH, "Bitte dr"+u2+"cken Sie {0}, um die Installation abzuschliessen." }, // finish + { PREVIOUS, "Zur"+u2+"ck" }, // button + { PROGRESS, "Fortschritt" }, // progress + { README, "README" }, // readme + { SELECT, "Ausw"+a2+"hlen" }, // button (approval in JFileChooser) + { SELECT_INSTALLATION_TYPE, "Bitte w"+a2+"hlen Sie den Installationstyp" }, // installation type + { SELECT_JAVA_HOME, "Bitte w"+a2+"hlen Sie das Java Home Verzeichnis" }, // directory + { SELECT_LANGUAGE, "Bitte w"+a2+"hlen Sie Ihre Sprache" }, // language + { SELECT_TARGET_DIRECTORY, "Bitte w"+a2+"hlen Sie das Zielverzeichnis" }, // directory + { SOURCES, "Quellcode" }, // installation type + { STANDARD, "Standard (Kern, Bibliotheksmodule, Demos, Beispiele, Dokumentation)" }, // installation type + { STANDALONE, "Standalone (ein ausf"+u2+"hrbares .jar File)" }, // installation type + { SUCCESS, "Sie haben Jython {0} erfolgreich im Verzeichnis {1} installiert." }, // final + { TARGET_DIRECTORY_PROPERTY, "Zielverzeichnis" }, // property als Titel + { TARGET_JAVA_HOME_PROPERTY, "Java Home Verzeichnis" }, // property als Titel + { UNABLE_CREATE_DIRECTORY, "Fehler beim Erstellen von Verzeichnis {0}." }, // error + { UNABLE_CREATE_FILE, "Fehler beim Erstellen von File {0}." }, // error + { UNABLE_TO_DELETE, "Fehler beim L"+o2+"schen von {0}" }, // console + { UNEXPECTED_URL, "Das Jar File f"+u2+"r die Installation weist eine unerwartete URL {0} auf." }, // error + { VERSION_INFO, "Sie sind im Begriff, Jython Version {0} zu installieren." }, // version + { WELCOME_TO_JYTHON, "Willkommen bei Jython !" }, // welcome + { ZIP_ENTRY_SIZE, "Der Zip Eintrag {0} hat eine unbekannte Gr"+o2+"sse." }, // error + { ZIP_ENTRY_TOO_BIG, "Der Zip Eintrag {0} ist zu gross." }, // error + + // Konsole Texte (beginnend mit C_) duerfen keine Umlaute und andere Sonderzeichen enthalten: + // console texts (beginning with C_) must not contain special characters (use ASCII only): + { C_ACCEPT, "Akzeptieren Sie die Lizenzvereinbarung ?" }, // license + { C_ALL, "Alles (volle Installation, inklusive Quellcode)" }, // installation type + { C_AT_ANY_TIME_CANCEL, "(Sie koennen die Installation jederzeit durch Eingabe von {0} abbrechen)" }, // console + { C_AVAILABLE_LANGUAGES, "Die folgenden Sprachen sind fuer den Installationsvorgang verfuegbar: {0}" }, // languages + { C_CHECK_JAVA_VERSION, "Ueberpruefung der Java Version ..." }, // progress + { C_CLEAR_DIRECTORY, "Der Inhalt von Verzeichnis {0} wird anschliessend geloescht! Moechten Sie wirklich weiterfahren ?" }, //console + { C_CONFIRM_TARGET, "Bitte bestaetigen Sie den Start des Kopiervorgangs ins Verzeichnis {0}" }, // console + { C_CONGRATULATIONS, "Gratulation!" }, // congratulations + { C_CREATE_DIRECTORY, "Das Verzeichnis {0} gibt es nicht - soll es erstellt werden ?" }, // console + { C_ENTER_TARGET_DIRECTORY, "Bitte geben Sie das Zielverzeichnis ein" }, // console + { C_ENTER_JAVA_HOME, "Bitte geben Sie das gewuenschte Java Home Verzeichnis ein (Enter fuer das aktuelle)" }, // console + { C_ENGLISH, "Englisch" }, // language + { C_EXCLUDE, "Moechten Sie Teile von der Installation ausschliessen ?" }, // installation type + { C_GENERATING_START_SCRIPTS, "Start Scripts werden generiert ..." }, // progress + { C_GERMAN, "Deutsch" }, // language + { C_INCLUDE, "Moechten Sie weitere Teile installieren ?" }, // installation type + { C_INEXCLUDE_PARTS, "Folgende Teile stehen zur Auswahl ({0} = keine weiteren)" }, // installation type + { C_INSTALL_TYPES, "Die folgenden Installationstypen sind verfuegbar:" }, // installation type + { C_INVALID_ANSWER, "Die Antwort {0} ist hier nicht gueltig" }, // error + { C_JAVA_VERSION, "Ihre Java Version fuer den Start von Jython ist: {0} / {1}" }, // version + { C_MINIMUM, "Minimum (Kern)" }, // installation type + { C_NO, "n" }, // answer + { C_NO_BIN_DIRECTORY, "Es gibt kein /bin Verzeichnis unterhalb {0}." }, //error + { C_NO_JAVA_EXECUTABLE, "Es gibt kein ausfuehrbares java in {0}." }, // error + { C_NO_VALID_JAVA, "Keine gueltige Java Version gefunden in {0}." }, // error + { C_NON_EMPTY_TARGET_DIRECTORY, "Das Zielverzeichnis {0} enthaelt bereits Daten" }, // error + { C_NOT_A_DIRECTORY, "{0} ist kein Verzeichnis. " }, // error + { C_NOT_FOUND, "{0} nicht gefunden." }, // error + { C_OS_VERSION, "Ihre Betriebssystem Version ist: {0} / {1}" }, // version + { C_OVERWRITE_DIRECTORY, "Das Verzeichnis {0} enthaelt bereits Daten, und die Installation wuerde diese ueberschreiben - ok ?" }, // console + { C_PACKING_STANDALONE_JAR, "Das standalone " + JarInstaller.JYTHON_JAR + " File wird erstellt ..." }, // progress + { C_PROCEED, "Bitte druecken Sie Enter um weiterzufahren" }, // console + { C_PROCEED_ANYWAY, "Bitte druecken Sie Enter um trotzdem weiterzufahren" }, // console + { C_READ_LICENSE, "Moechten Sie die Lizenzvereinbarung jetzt lesen ?" }, // license + { C_READ_README, "Moechten Sie den Inhalt von README jetzt lesen ?" }, // readme + { C_SCHEDULED, "{0} zur Installation vorgemerkt" }, // installation type + { C_SELECT_INSTALL_TYPE, "Bitte waehlen Sie den Installationstyp" }, // installation type + { C_SELECT_LANGUAGE, "Bitte waehlen Sie Ihre Sprache" }, // language + { C_SILENT_INSTALLATION, "Die Installation wird ohne Benutzerinteraktion ausgefuehrt" }, // installation mode + { C_STANDALONE, "Standalone (ein ausfuehrbares .jar File)" }, //installation mode + { C_STANDARD, "Standard (Kern, Bibliotheksmodule, Demos und Beispiele, Dokumentation)" }, // installation type + { C_SUCCESS, "Sie haben Jython {0} erfolgreich im Verzeichnis {1} installiert." }, // final + { C_SUMMARY, "Zusammenfassung:" }, // summary + { C_TO_CURRENT_JAVA, "Warnung: Wechsel zum aktuellen JDK wegen Fehler: {0}." }, // warning + { C_UNABLE_CREATE_DIRECTORY, "Fehler beim Erstellen von Verzeichnis {0}." }, // error + { C_UNABLE_CREATE_TMPFILE, "Fehler beim Erstellen der temporaeren Datei {0}." }, // error + { C_UNABLE_TO_DELETE, "Fehler beim Loeschen von {0}" }, // console + { C_UNSCHEDULED, "{0} von der Installation ausgeschlossen" }, // installation type + { C_UNSUPPORTED_JAVA, "Diese Java Version ist nicht unterstuetzt." }, // version + { C_UNSUPPORTED_OS, "Dieses Betriebssystem ist eventuell nicht vollstaendig unterstuetzt." }, // version + { C_USING_TYPE, "Installationstyp ist {0}" }, // installation type + { C_VERSION_INFO, "Sie sind im Begriff, Jython Version {0} zu installieren." }, // version + { C_WELCOME_TO_JYTHON, "Willkommen bei Jython !" }, // welcome + { C_YES, "j" }, // answer + + }; + + public Object[][] getContents() { + return contents; + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextConstants_en.java b/installer/src/java/org/python/util/install/TextConstants_en.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextConstants_en.java @@ -0,0 +1,5 @@ +package org.python.util.install; + +public class TextConstants_en extends TextConstants { + // need this as placeholder +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TextKeys.java b/installer/src/java/org/python/util/install/TextKeys.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TextKeys.java @@ -0,0 +1,135 @@ +package org.python.util.install; + +public interface TextKeys { + public static final String ACCEPT = "ACCEPT"; + public static final String ACCEPT_PROPERTY = "ACCEPT_PROPERTY"; + public static final String ALL = "ALL"; + public static final String BROWSE = "BROWSE"; + public static final String CANCEL = "CANCEL"; + public static final String CHOOSE_LOCATION = "CHOOSE_LOCATION"; + public static final String CHOOSE_JRE = "CHOOSE_JRE"; + public static final String CONFIRM_START = "CONFIRM_START"; + public static final String CONGRATULATIONS = "CONGRATULATIONS"; + public static final String CORE = "CORE"; + public static final String CREATED_DIRECTORY = "CREATED_DIRECTORY"; + public static final String CURRENT = "CURRENT"; + public static final String CUSTOM = "CUSTOM"; + public static final String DEMOS_EXAMPLES = "DEMOS_EXAMPLES"; + public static final String DIRECTORIES_ONLY = "DIRECTORIES_ONLY"; + public static final String DOCUMENTATION = "DOCUMENTATION"; + public static final String DO_NOT_ACCEPT = "DO_NOT_ACCEPT"; + public static final String EMPTY_TARGET_DIRECTORY = "EMPTY_TARGET_DIRECTORY"; + public static final String ENGLISH = "ENGLISH"; + public static final String ERROR = "ERROR"; + public static final String ERROR_ACCESS_JARFILE = "ERROR_ACCESS_JARFILE"; + public static final String FINISH = "FINISH"; + public static final String GENERATING_START_SCRIPTS = "GENERATE_START_SCRIPTS"; + public static final String GERMAN = "GERMAN"; + public static final String INFLATING = "INFLATING"; + public static final String INFORMATION = "INFORMATION"; + public static final String INSTALLATION_CANCELLED = "INSTALLATION_CANCELLED"; + public static final String INSTALLATION_IN_PROGRESS = "INSTALLATION_IN_PROGRESS"; + public static final String INSTALLATION_TYPE_DESCRIPTION = "INSTALLATION_TYPE_DESCRIPTION"; + public static final String INSTALLATION_TYPE = "INSTALLATION_TYPE"; + public static final String JAR_NOT_FOUND = "JAR_NOT_FOUND"; + public static final String JAVA_INFO = "JAVA_INFO"; + public static final String JYTHON_INSTALL = "JYTHON_INSTALL"; + public static final String LANGUAGE_PROPERTY = "LANGUAGE_PROPERTY"; + public static final String LIBRARY_MODULES = "LIBRARY_MODULES"; + public static final String LICENSE = "LICENSE"; + public static final String MAYBE_NOT_SUPPORTED = "MAYBE_NOT_SUPPORTED"; + public static final String MINIMUM = "MINIMUM"; + public static final String NEXT = "NEXT"; + public static final String NON_EMPTY_TARGET_DIRECTORY = "NON_EMPTY_TARGET_DIRECTORY"; + public static final String NO_MANIFEST = "NO_MANIFEST"; + public static final String NOT_OK = "NOT_OK"; + public static final String OK = "OK"; + public static final String OS_INFO = "OS_INFO"; + public static final String OTHER = "OTHER"; + public static final String OVERVIEW_DESCRIPTION = "OVERVIEW_DESCRIPTION"; + public static final String OVERVIEW_TITLE = "OVERVIEW_TITLE"; + public static final String PACKING_STANDALONE_JAR = "PACKING_STANDALONE_JAR"; + public static final String PLEASE_ACCEPT_LICENSE = "PLEASE_ACCEPT_LICENSE"; + public static final String PLEASE_README = "PLEASE_README"; + public static final String PLEASE_READ_LICENSE = "PLEASE_READ_LICENSE"; + public static final String PLEASE_WAIT = "PLEASE_WAIT"; + public static final String PRESS_FINISH = "PRESS_FINISH"; + public static final String PREVIOUS = "PREVIOUS"; + public static final String PROGRESS = "PROGRESS"; + public static final String README = "README"; + public static final String SELECT = "SELECT"; + public static final String SELECT_INSTALLATION_TYPE = "SELECT_INSTALLATION_TYPE"; + public static final String SELECT_JAVA_HOME = "SELECT_JAVA_HOME"; + public static final String SELECT_LANGUAGE = "SELECT_LANGUAGE"; + public static final String SELECT_TARGET_DIRECTORY = "SELECT_TARGET_DIRECTORY"; + public static final String SOURCES = "SOURCES"; + public static final String STANDARD = "STANDARD"; + public static final String STANDALONE = "STANDALONE"; + public static final String SUCCESS = "SUCCESS"; + public static final String TARGET_DIRECTORY_PROPERTY = "TARGET_DIRECTORY_PROPERTY"; + public static final String TARGET_JAVA_HOME_PROPERTY = "TARGET_JAVA_HOME_PROPERTY"; + public static final String UNABLE_CREATE_DIRECTORY = "UNABLE_CREATE_DIRECTORY"; + public static final String UNABLE_CREATE_FILE = "UNABLE_CREATE_FILE"; + public static final String UNABLE_TO_DELETE = "UNABLE_TO_DELETE"; + public static final String UNEXPECTED_URL = "UNEXPECTED_URL"; + public static final String VERSION_INFO = "VERSION_INFO"; + public static final String WELCOME_TO_JYTHON = "WELCOME_TO_JYTHON"; + public static final String ZIP_ENTRY_SIZE = "ZIP_ENTRY_SIZE"; + public static final String ZIP_ENTRY_TOO_BIG = "ZIP_ENTRY_TOO_BIG"; + + // console texts + public static final String C_ACCEPT = "C_ACCEPT"; + public static final String C_ALL = "C_ALL"; + public static final String C_AT_ANY_TIME_CANCEL = "C_AT_ANY_TIME_CANCEL"; + public static final String C_AVAILABLE_LANGUAGES = "C_AVAILABLE_LANGUAGES"; + public static final String C_CHECK_JAVA_VERSION = "C_CHECK_JAVA_VERSION"; + public static final String C_CLEAR_DIRECTORY = "C_CLEAR_DIRECTORY"; + public static final String C_CONFIRM_TARGET = "C_CONFIRM_TARGET"; + public static final String C_CONGRATULATIONS = "C_CONGRATULATIONS"; + public static final String C_CREATE_DIRECTORY = "C_CREATE_DIRECTORY"; + public static final String C_ENGLISH = "C_ENGLISH"; + public static final String C_ENTER_TARGET_DIRECTORY = "C_ENTER_TARGET_DIRECTORY"; + public static final String C_ENTER_JAVA_HOME = "C_ENTER_JAVA_HOME"; + public static final String C_EXCLUDE = "C_EXCLUDE"; + public static final String C_GENERATING_START_SCRIPTS = "C_GENERATE_START_SCRIPTS"; + public static final String C_GERMAN = "C_GERMAN"; + public static final String C_INCLUDE = "C_INCLUDE"; + public static final String C_INEXCLUDE_PARTS = "C_INEXCLUDE_PARTS"; + public static final String C_INSTALL_TYPES = "C_INSTALL_TYPES"; + public static final String C_INVALID_ANSWER = "C_INVALID_ANSWER"; + public static final String C_JAVA_VERSION = "C_JAVA_VERSION"; + public static final String C_MINIMUM = "C_MINIMUM"; + public static final String C_NO = "C_NO"; + public static final String C_NO_BIN_DIRECTORY = "C_NO_BIN_DIRECTORY"; + public static final String C_NO_JAVA_EXECUTABLE = "C_NO_JAVA_EXECUTABLE"; + public static final String C_NO_VALID_JAVA = "C_NO_VALID_JAVA"; + public static final String C_NON_EMPTY_TARGET_DIRECTORY = "C_NON_EMPTY_TARGET_DIRECTORY"; + public static final String C_NOT_A_DIRECTORY = "C_NOT_A_DIRECTORY"; + public static final String C_NOT_FOUND = "C_NOT_FOUND"; + public static final String C_OS_VERSION = "C_OS_VERSION"; + public static final String C_OVERWRITE_DIRECTORY = "C_OVERWRITE_DIRECTORY"; + public static final String C_PACKING_STANDALONE_JAR = "C_PACKING_STANDALONE_JAR"; + public static final String C_PROCEED = "C_PROCEED"; + public static final String C_PROCEED_ANYWAY = "C_PROCEED_ANYWAY"; + public static final String C_READ_LICENSE = "C_READ_LICENSE"; + public static final String C_READ_README = "C_READ_README"; + public static final String C_SCHEDULED = "C_SCHEDULED"; + public static final String C_SELECT_INSTALL_TYPE = "C_SELECT_INSTALL_TYPE"; + public static final String C_SELECT_LANGUAGE = "C_SELECT_LANGUAGE"; + public static final String C_SILENT_INSTALLATION = "C_SILENT_INSTALLATION"; + public static final String C_STANDALONE = "C_STANDALONE"; + public static final String C_STANDARD = "C_STANDARD"; + public static final String C_SUCCESS = "C_SUCCESS"; + public static final String C_SUMMARY = "C_SUMMARY"; + public static final String C_UNABLE_CREATE_TMPFILE = "C_UNABLE_CREATE_TMPFILE"; + public static final String C_TO_CURRENT_JAVA = "C_TO_CURRENT_JAVA"; + public static final String C_UNABLE_CREATE_DIRECTORY = "C_UNABLE_CREATE_DIRECTORY"; + public static final String C_UNABLE_TO_DELETE = "C_UNABLE_TO_DELETE"; + public static final String C_UNSCHEDULED = "C_UNSCHEDULED"; + public static final String C_UNSUPPORTED_JAVA = "C_UNSUPPORTED_JAVA"; + public static final String C_UNSUPPORTED_OS = "C_UNSUPPORTED_OS"; + public static final String C_USING_TYPE = "C_USING_TYPE"; + public static final String C_VERSION_INFO = "C_VERSION_INFO"; + public static final String C_WELCOME_TO_JYTHON = "C_WELCOME_TO_JYTHON"; + public static final String C_YES = "C_YES"; +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/TypePage.java b/installer/src/java/org/python/util/install/TypePage.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/TypePage.java @@ -0,0 +1,268 @@ +package org.python.util.install; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.ButtonGroup; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButton; + +public class TypePage extends AbstractWizardPage { + + private static final String _CUSTOM_ACTION_COMMAND = "custom"; + + private JLabel _label; + private JRadioButton _allButton; + private JRadioButton _standardButton; + private JRadioButton _minimumButton; + private JRadioButton _standaloneButton; + private JRadioButton _customButton; + + private JCheckBox _core; + private JCheckBox _mod; + private JCheckBox _demo; + private JCheckBox _doc; + private JCheckBox _src; + + private boolean _firstTime = true; + + public TypePage() { + super(); + initComponents(); + } + + private void initComponents() { + TypeChangeListener typeChangeListener = new TypeChangeListener(); + + _label = new JLabel(); + + // radio buttons + _allButton = new JRadioButton(); + _allButton.setActionCommand(Installation.ALL); + _allButton.addActionListener(typeChangeListener); + _standardButton = new JRadioButton(); + _standardButton.setActionCommand(Installation.STANDARD); + _standardButton.addActionListener(typeChangeListener); + _minimumButton = new JRadioButton(); + _minimumButton.setActionCommand(Installation.MINIMUM); + _minimumButton.addActionListener(typeChangeListener); + _standaloneButton = new JRadioButton(); + _standaloneButton.setActionCommand(Installation.STANDALONE); + _standaloneButton.addActionListener(typeChangeListener); + _customButton = new JRadioButton(); + _customButton.setActionCommand(_CUSTOM_ACTION_COMMAND); + _customButton.addActionListener(typeChangeListener); + ButtonGroup radioButtonGroup = new ButtonGroup(); + radioButtonGroup.add(_allButton); + radioButtonGroup.add(_standardButton); + radioButtonGroup.add(_minimumButton); + radioButtonGroup.add(_standaloneButton); + radioButtonGroup.add(_customButton); + JPanel radioPanel = new JPanel(new GridLayout(0, 1)); + radioPanel.add(_allButton); + radioPanel.add(_standardButton); + radioPanel.add(_minimumButton); + radioPanel.add(_standaloneButton); + radioPanel.add(_customButton); + + // check boxes + _core = new JCheckBox(); + _core.setEnabled(false); + _mod = new JCheckBox(); + _mod.setEnabled(true); + _mod.setActionCommand(InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES); + _mod.addActionListener(typeChangeListener); + _demo = new JCheckBox(); + _demo.setEnabled(true); + _demo.setActionCommand(InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES); + _demo.addActionListener(typeChangeListener); + _doc = new JCheckBox(); + _doc.setEnabled(true); + _doc.setActionCommand(InstallerCommandLine.INEXCLUDE_DOCUMENTATION); + _doc.addActionListener(typeChangeListener); + _src = new JCheckBox(); + _src.setEnabled(true); + _src.setActionCommand(InstallerCommandLine.INEXCLUDE_SOURCES); + _src.addActionListener(typeChangeListener); + + JPanel checkboxPanel = new JPanel(); + GridLayout gridLayout = new GridLayout(5, 1); + checkboxPanel.setLayout(gridLayout); + checkboxPanel.add(_core); + checkboxPanel.add(_mod); + checkboxPanel.add(_demo); + checkboxPanel.add(_doc); + checkboxPanel.add(_src); + + JPanel panel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + panel.setLayout(gridBagLayout); + GridBagConstraints gridBagConstraints = newGridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridwidth = 2; + panel.add(_label, gridBagConstraints); + gridBagConstraints.gridwidth = 1; + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + panel.add(radioPanel, gridBagConstraints); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + panel.add(checkboxPanel, gridBagConstraints); + + add(panel); + } + + protected String getTitle() { + return getText(INSTALLATION_TYPE); + } + + protected String getDescription() { + return getText(INSTALLATION_TYPE_DESCRIPTION); + } + + protected boolean isCancelVisible() { + return true; + } + + protected boolean isPreviousVisible() { + return true; + } + + protected boolean isNextVisible() { + return true; + } + + protected JComponent getFocusField() { + InstallationType installationType = getInstallationType(); + if (installationType.isAll()) { + return _allButton; + } else if (installationType.isMinimum()) { + return _minimumButton; + } else if (installationType.isStandalone()) { + return _standaloneButton; + } else if (installationType.isStandard()) { + return _standardButton; + } else { + return _customButton; + } + } + + protected void activate() { + _label.setText(getText(SELECT_INSTALLATION_TYPE) + ": "); + _allButton.setText(getText(ALL)); + _standardButton.setText(getText(STANDARD)); + _minimumButton.setText(getText(MINIMUM)); + _standaloneButton.setText(getText(STANDALONE)); + _customButton.setText(getText(CUSTOM)); + InstallationType installationType = getInstallationType(); + if (installationType.isAll()) { + _allButton.setSelected(true); + } else if (installationType.isMinimum()) { + _minimumButton.setSelected(true); + } else if (installationType.isStandalone()) { + _standaloneButton.setSelected(true); + } else if (installationType.isStandard()) { + _standardButton.setSelected(true); + } else { + _customButton.setSelected(true); + } + _core.setText(getText(CORE)); + _mod.setText(getText(LIBRARY_MODULES)); + _demo.setText(getText(DEMOS_EXAMPLES)); + _doc.setText(getText(DOCUMENTATION)); + _src.setText(getText(SOURCES)); + setCheckboxes(installationType); + } + + protected void passivate() { + } + + protected void beforeValidate() { + } + + private InstallationType getInstallationType() { + InstallationType installationType; + if (_firstTime) { + _firstTime = false; + installationType = new InstallationType(); + installationType.setStandard(); + FrameInstaller.setInstallationType(installationType); + } + installationType = FrameInstaller.getInstallationType(); + return installationType; + } + + private final class TypeChangeListener implements ActionListener { + public void actionPerformed(ActionEvent e) { + InstallationType installationType = FrameInstaller.getInstallationType(); + String actionCommand = e.getActionCommand(); + if (Installation.ALL.equals(actionCommand)) { + installationType.setAll(); + setCheckboxes(installationType); + } else if (Installation.STANDARD.equals(actionCommand)) { + installationType.setStandard(); + setCheckboxes(installationType); + } else if (Installation.MINIMUM.equals(actionCommand)) { + installationType.setMinimum(); + setCheckboxes(installationType); + } else if (Installation.STANDALONE.equals(actionCommand)) { + installationType.setStandalone(); + setCheckboxes(installationType); + } else if (_CUSTOM_ACTION_COMMAND.equals(actionCommand)) { + _mod.setEnabled(true); + _demo.setEnabled(true); + _doc.setEnabled(true); + _src.setEnabled(true); + } else { + boolean selected = ((JCheckBox) e.getSource()).isSelected(); + if (InstallerCommandLine.INEXCLUDE_LIBRARY_MODULES.equals(actionCommand)) { + if (selected) { + installationType.addLibraryModules(); + } else { + installationType.removeLibraryModules(); + } + } else if (InstallerCommandLine.INEXCLUDE_DEMOS_AND_EXAMPLES.equals(actionCommand)) { + if (selected) { + installationType.addDemosAndExamples(); + } else { + installationType.removeDemosAndExamples(); + } + } else if (InstallerCommandLine.INEXCLUDE_DOCUMENTATION.equals(actionCommand)) { + if (selected) { + installationType.addDocumentation(); + } else { + installationType.removeDocumentation(); + } + } else if (InstallerCommandLine.INEXCLUDE_SOURCES.equals(actionCommand)) { + if (selected) { + installationType.addSources(); + } else { + installationType.removeSources(); + } + } + } + FrameInstaller.setInstallationType(installationType); + } + } + + void setCheckboxes(InstallationType installationType) { + _core.setSelected(true); + _mod.setSelected(installationType.installLibraryModules()); + _demo.setSelected(installationType.installDemosAndExamples()); + _doc.setSelected(installationType.installDocumentation()); + _src.setSelected(installationType.installSources()); + _standaloneButton.setSelected(installationType.isStandalone()); + _mod.setEnabled(!installationType.isPredefined()); + _demo.setEnabled(!installationType.isPredefined()); + _doc.setEnabled(!installationType.isPredefined()); + _src.setEnabled(!installationType.isPredefined()); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/UnicodeSequences.java b/installer/src/java/org/python/util/install/UnicodeSequences.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/UnicodeSequences.java @@ -0,0 +1,19 @@ +package org.python.util.install; + +/** + * Some unicode special characters + * + * @see http://www.unicode.org/charts/PDF/U0080.pdf + */ +public interface UnicodeSequences { + + public static final String A2 = "\u00C4"; // German A umlaut: A with two dots above + public static final String a2 = "\u00E4"; // German a umlaut: a with two dots above + + public static final String O2 = "\u00D6"; // German O umlaut: O with two dots above + public static final String o2 = "\u00F6"; // German o umlaut: o with two dots above + + public static final String U2 = "\u00DC"; // German U umlaut: U with two dots above + public static final String u2 = "\u00FC"; // German u umlaut: u with two dots above + +} diff --git a/installer/src/java/org/python/util/install/ValidationEvent.java b/installer/src/java/org/python/util/install/ValidationEvent.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationEvent.java @@ -0,0 +1,14 @@ +package org.python.util.install; + +import java.util.EventObject; + +public class ValidationEvent extends EventObject { + + public ValidationEvent(AbstractWizardPage source) { + super(source); + } + + public AbstractWizardPage getWizardPage() { + return (AbstractWizardPage) source; + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ValidationException.java b/installer/src/java/org/python/util/install/ValidationException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationException.java @@ -0,0 +1,19 @@ +package org.python.util.install; + +public class ValidationException extends Exception { + public ValidationException() { + super(); + } + + public ValidationException(String message) { + super(message); + } + + public ValidationException(String message, Throwable cause) { + super(message, cause); + } + + public ValidationException(Throwable cause) { + super(cause); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ValidationInformationException.java b/installer/src/java/org/python/util/install/ValidationInformationException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationInformationException.java @@ -0,0 +1,21 @@ +package org.python.util.install; + +public class ValidationInformationException extends Exception { + + public ValidationInformationException() { + super(); + } + + public ValidationInformationException(String message) { + super(message); + } + + public ValidationInformationException(String message, Throwable cause) { + super(message, cause); + } + + public ValidationInformationException(Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/ValidationListener.java b/installer/src/java/org/python/util/install/ValidationListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/ValidationListener.java @@ -0,0 +1,11 @@ +package org.python.util.install; + +public interface ValidationListener { + public void validationFailed(ValidationEvent event, ValidationException exception); + + public void validationInformationRequired(ValidationEvent event, ValidationInformationException exception); + + public void validationStarted(ValidationEvent event); + + public void validationSucceeded(ValidationEvent event); +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/Wizard.java b/installer/src/java/org/python/util/install/Wizard.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/Wizard.java @@ -0,0 +1,90 @@ +package org.python.util.install; + +import java.awt.Dimension; +import java.awt.Rectangle; + +import javax.swing.JOptionPane; + +import org.python.util.install.driver.Autotest; + +public class Wizard extends AbstractWizard implements TextKeys { + + public Wizard(JarInfo jarInfo, Autotest autotest) { + super(); + + setTitle(getText(JYTHON_INSTALL)); + + LanguagePage languagePage = new LanguagePage(jarInfo); + LicensePage licensePage = new LicensePage(jarInfo); + licensePage.setValidator(new LicensePageValidator(licensePage)); + TypePage typePage = new TypePage(); + DirectorySelectionPage directoryPage = new DirectorySelectionPage(jarInfo); + directoryPage.setValidator(new DirectorySelectionPageValidator(directoryPage)); + JavaSelectionPage javaPage = new JavaSelectionPage(); + javaPage.setValidator(new JavaSelectionPageValidator(javaPage)); + OverviewPage overviewPage = new OverviewPage(); + ProgressPage progressPage = new ProgressPage(jarInfo, autotest); + ReadmePage readmePage = new ReadmePage(jarInfo); + SuccessPage successPage = new SuccessPage(jarInfo); + + this.addPage(languagePage); + this.addPage(licensePage); + this.addPage(typePage); + this.addPage(directoryPage); + this.addPage(javaPage); + this.addPage(overviewPage); + this.addPage(progressPage); + this.addPage(readmePage); + this.addPage(successPage); + + setSize(720, 330); + centerOnScreen(); + validate(); + } + + protected boolean finish() { + return true; + } + + protected String getCancelString() { + return getText(CANCEL); + } + + protected String getFinishString() { + return getText(FINISH); + } + + protected String getNextString() { + return getText(NEXT); + } + + protected String getPreviousString() { + return getText(PREVIOUS); + } + + public void validationStarted(ValidationEvent event) { + } + + public void validationFailed(ValidationEvent event, ValidationException exception) { + JOptionPane.showMessageDialog(this, exception.getMessage(), getText(TextKeys.ERROR), JOptionPane.ERROR_MESSAGE); + } + + public void validationInformationRequired(ValidationEvent event, ValidationInformationException exception) { + JOptionPane.showMessageDialog(this, exception.getMessage(), getText(INFORMATION), + JOptionPane.INFORMATION_MESSAGE); + } + + public void validationSucceeded(ValidationEvent event) { + } + + private final String getText(String textKey) { + return Installation.getText(textKey); + } + + private void centerOnScreen() { + Dimension dim = getToolkit().getScreenSize(); + Rectangle rectBounds = getBounds(); + setLocation((dim.width - rectBounds.width) / 2, (dim.height - rectBounds.height) / 2); + } + +} diff --git a/installer/src/java/org/python/util/install/WizardEvent.java b/installer/src/java/org/python/util/install/WizardEvent.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/WizardEvent.java @@ -0,0 +1,13 @@ +package org.python.util.install; + +import java.util.EventObject; + +public class WizardEvent extends EventObject { + public WizardEvent(AbstractWizard source) { + super(source); + } + + public AbstractWizard getWizard() { + return (AbstractWizard) source; + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/WizardHeader.java b/installer/src/java/org/python/util/install/WizardHeader.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/WizardHeader.java @@ -0,0 +1,89 @@ +package org.python.util.install; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JSeparator; + +public class WizardHeader extends AbstractWizardHeader { + private static final Dimension _iconSize = new Dimension(100, 60); + + private JLabel _descriptionLabel; + private JSeparator _headerSeparator; + private JLabel _iconLabel; + private JLabel _titleLabel; + + WizardHeader() { + super(); + initComponents(); + } + + private void initComponents() { + GridBagConstraints gridBagConstraints; + + _titleLabel = new JLabel(); + _descriptionLabel = new JLabel(); + _iconLabel = new JLabel(); + _headerSeparator = new JSeparator(); + + setLayout(new GridBagLayout()); + + setBackground(new Color(255, 255, 255)); + _titleLabel.setFont(_titleLabel.getFont().deriveFont(Font.BOLD, 14f)); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.insets = new Insets(2, 2, 2, 2); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + add(_titleLabel, gridBagConstraints); + + _descriptionLabel.setFont(_descriptionLabel.getFont().deriveFont(Font.PLAIN)); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.insets = new Insets(2, 7, 2, 2); + gridBagConstraints.anchor = GridBagConstraints.NORTHWEST; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + add(_descriptionLabel, gridBagConstraints); + + _iconLabel.setMinimumSize(_iconSize); + _iconLabel.setMaximumSize(_iconSize); + _iconLabel.setPreferredSize(_iconSize); + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 0; + gridBagConstraints.gridheight = 2; + gridBagConstraints.insets = new Insets(2, 2, 2, 2); + gridBagConstraints.anchor = GridBagConstraints.NORTHEAST; + add(_iconLabel, gridBagConstraints); + + gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.gridwidth = 2; + gridBagConstraints.fill = GridBagConstraints.HORIZONTAL; + gridBagConstraints.weightx = 1.0; + add(_headerSeparator, gridBagConstraints); + } + + protected void setDescription(String description) { + _descriptionLabel.setText(description); + } + + protected void setIcon(ImageIcon icon) { + _iconLabel.setIcon(icon); + } + + protected void setTitle(String title) { + _titleLabel.setText(title); + } +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/WizardListener.java b/installer/src/java/org/python/util/install/WizardListener.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/WizardListener.java @@ -0,0 +1,13 @@ +package org.python.util.install; + +public interface WizardListener { + public void wizardCancelled(WizardEvent event); + + public void wizardFinished(WizardEvent event); + + public void wizardNext(WizardEvent event); + + public void wizardPrevious(WizardEvent event); + + public void wizardStarted(WizardEvent event); +} \ No newline at end of file diff --git a/installer/src/java/org/python/util/install/driver/Autotest.java b/installer/src/java/org/python/util/install/driver/Autotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/Autotest.java @@ -0,0 +1,274 @@ +package org.python.util.install.driver; + +import java.io.File; +import java.io.IOException; + +import org.python.util.install.FileHelper; +import org.python.util.install.Installation; +import org.python.util.install.InstallationListener; +import org.python.util.install.InstallerCommandLine; +import org.python.util.install.JavaHomeHandler; +import org.python.util.install.JavaVersionTester; +import org.python.util.install.Installation.JavaVersionInfo; + +public abstract class Autotest implements InstallationListener { + + private static final String _DIR_SUFFIX = "_dir"; + + private static int _count = 0; // unique test number + private static File _rootDirectory = null; + private static JavaVersionInfo _systemDefaultJavaVersion; + + private String _name; + private File _targetDir; + private JavaHomeHandler _javaHomeHandler; + private boolean _verbose; + private String[] _commandLineArgs; + private Verifier _verifier; + + /** + * constructor + * + * @throws IOException + * @throws DriverException + */ + protected Autotest(InstallerCommandLine commandLine) throws IOException, DriverException { + _count++; + buildName(); + if (_rootDirectory == null) { + createRootDirectory(); + } + createTargetDirectory(); + setCommandLineArgs(new String[0]); // a priori value + _verbose = commandLine.hasVerboseOption(); + _javaHomeHandler = commandLine.getJavaHomeHandler(); + } + + /** + * @return the root directory for all test installations + */ + protected static File getRootDir() { + return _rootDirectory; + } + + /** + * @return the target directory of this test + */ + protected File getTargetDir() { + return _targetDir; + } + + /** + * @return the name of this test + */ + protected String getName() { + return _name; + } + + /** + * @return the array of command line arguments + */ + protected String[] getCommandLineArgs() { + return _commandLineArgs; + } + + /** + * set the array of command line arguments + * + * @param commandLineArgs + */ + protected void setCommandLineArgs(String[] commandLineArgs) { + _commandLineArgs = commandLineArgs; + } + + /** + * @return the java home handler, can be asked for deviation using isDeviation(). + */ + protected JavaHomeHandler getJavaHomeHandler() { + return _javaHomeHandler; + } + + /** + * @return true if this test should be verbose + */ + protected boolean isVerbose() { + return _verbose; + } + + /** + * @return the name suffix for this test + */ + protected abstract String getNameSuffix(); + + /** + * @throws DriverException if the target directory does not exist or is empty (installation failed) + */ + protected void assertTargetDirNotEmpty() throws DriverException { + File targetDir = getTargetDir(); + if (targetDir != null) { + if (targetDir.exists() && targetDir.isDirectory()) { + if (targetDir.listFiles().length > 0) { + return; + } + } + } + throw new DriverException("installation failed for " + targetDir.getAbsolutePath()); + } + + /** + * Convenience method to add the additional arguments, if specified behind the -A option + *

+ * This adds (if present): + *

    + *
  • target directory (should always be present) + *
  • verbose + *
  • jre + *
+ */ + protected void addAdditionalArguments() { + if (getTargetDir() != null) { + addArgument("-d"); + addArgument(getTargetDir().getAbsolutePath()); + } + if (isVerbose()) { + addArgument("-v"); + } + JavaHomeHandler javaHomeHandler = getJavaHomeHandler(); + if (javaHomeHandler.isDeviation() && javaHomeHandler.isValidHome()) { + addArgument("-j"); + addArgument(javaHomeHandler.getHome().getAbsolutePath()); + } + } + + /** + * Add an additional String argument to the command line arguments + * + * @param newArgument + */ + protected void addArgument(String newArgument) { + setCommandLineArgs(addArgument(newArgument, getCommandLineArgs())); + } + + /** + * set the verifier + */ + protected void setVerifier(Verifier verifier) { + _verifier = verifier; + _verifier.setTargetDir(getTargetDir()); + } + + protected Verifier getVerifier() { + return _verifier; + } + + // + // private stuff + // + + /** + * build a test name containing some special characters (which will be used to create the target + * directory), and store it into _name. + */ + private void buildName() { + StringBuilder b = new StringBuilder(24); + if (_count <= 99) { + b.append('0'); + } + if (_count <= 9) { + b.append('0'); + } + b.append(_count); + // explicitly use a blank, to nail down some platform specific problems + b.append(' '); + // add an exclamation mark if possible (see issue #1208) + if(canHandleExclamationMarks()) { + b.append('!'); + } + b.append(getNameSuffix()); + b.append('_'); + _name = b.toString(); + } + + /** + * Add the new argument to the args array + * + * @param newArgument + * @param args + * + * @return the new String array, with size increased by 1 + */ + private String[] addArgument(String newArgument, String[] args) { + String[] newArgs = new String[args.length + 1]; + for (int i = 0; i < args.length; i++) { + newArgs[i] = args[i]; + } + newArgs[args.length] = newArgument; + return newArgs; + } + + /** + * create the root directory for all automatic installations + *

+ * assumed to be a subdirectory of java.io.tmpdir + * + * @throws IOException + * @throws DriverException + */ + private void createRootDirectory() throws IOException, DriverException { + File tmpFile = File.createTempFile("jython.autoinstall.root_", _DIR_SUFFIX); + if (FileHelper.createTempDirectory(tmpFile)) { + _rootDirectory = tmpFile; + } else { + throw new DriverException("unable to create root temporary directory"); + } + } + + /** + * create a target directory for a test installation + * + * @throws IOException + * @throws DriverException + */ + private void createTargetDirectory() throws IOException, DriverException { + File tmpFile = File.createTempFile(getName(), _DIR_SUFFIX, _rootDirectory); + if (FileHelper.createTempDirectory(tmpFile)) { + _targetDir = tmpFile; + } else { + throw new DriverException("unable to create temporary target directory"); + } + } + + /** + * Determine if the target directory may contain an exclamation mark (see also issue #1208). + *

+ * Autotests can handle exclamation marks, if both the running and the system default java + * specification versions are 1.6 or higher. Class.getResource() was fixed for JDK 1.6, but only if the directory name does not end with '!'... + *

+ * Currently there is no way on windows, because the enabledelayedexpansion in jython.bat cannot + * handle exclamation marks in variable names. + * + * @return true if we can handle exclamation marks, false otherwise + */ + private boolean canHandleExclamationMarks() { + boolean exclamation = false; + if (!Installation.isWindows()) { + // get the running java specification version + String specificationVersion = System.getProperty(JavaVersionTester.JAVA_SPECIFICATION_VERSION, + ""); + if (Installation.getJavaSpecificationVersion(specificationVersion) > 15) { + // get the system default java version + if (_systemDefaultJavaVersion == null) { + _systemDefaultJavaVersion = Installation.getDefaultJavaVersion(); + } + if (_systemDefaultJavaVersion.getErrorCode() == Installation.NORMAL_RETURN) { + specificationVersion = _systemDefaultJavaVersion.getSpecificationVersion(); + if (Installation.getJavaSpecificationVersion(specificationVersion) > 15) { + exclamation = true; + } + } + } + } + return exclamation; + } + +} diff --git a/installer/src/java/org/python/util/install/driver/ConsoleAutotest.java b/installer/src/java/org/python/util/install/driver/ConsoleAutotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/ConsoleAutotest.java @@ -0,0 +1,30 @@ +package org.python.util.install.driver; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.python.util.install.InstallerCommandLine; + +public class ConsoleAutotest extends SilentAutotest { + + private Collection _answers; + + protected ConsoleAutotest(InstallerCommandLine commandLine) throws IOException, DriverException { + super(commandLine); + _answers = new ArrayList(50); + } + + protected void addAnswer(String answer) { + _answers.add(answer); + } + + protected Collection getAnswers() { + return _answers; + } + + protected String getNameSuffix() { + return "consoleTest"; + } + +} diff --git a/installer/src/java/org/python/util/install/driver/ConsoleDriver.java b/installer/src/java/org/python/util/install/driver/ConsoleDriver.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/ConsoleDriver.java @@ -0,0 +1,58 @@ +package org.python.util.install.driver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Collection; +import java.util.Iterator; + +/** + * A class driving another class, while the other class is performing console I/O. + * + *

+ *   (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+ *   (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+ * 
+ */ +public class ConsoleDriver extends Thread { + + private Tunnel _tunnel; + private Collection _answers; + + public ConsoleDriver(Tunnel tunnel, Collection answers) { + _tunnel = tunnel; + _answers = answers; + } + + /** + * Send answers in the correct sequence, as soon as the question is asked. + */ + public void run() { + Iterator answersIterator = _answers.iterator(); + while (answersIterator.hasNext()) { + String answer = (String) answersIterator.next(); + try { + readLine(); + sendAnswer(answer); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private void sendAnswer(String answer) throws IOException, InterruptedException { + Thread.sleep(100); // wait to be sure the question is really issued on the other end of the tunnel + System.out.println(" -> driving: '" + answer + "'"); + answer += Tunnel.NEW_LINE; + _tunnel.getAnswerSenderStream().write(answer.getBytes()); + _tunnel.getAnswerSenderStream().flush(); + } + + private void readLine() throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(_tunnel.getQuestionReceiverStream())); + reader.readLine(); + } + +} diff --git a/installer/src/java/org/python/util/install/driver/DriverException.java b/installer/src/java/org/python/util/install/driver/DriverException.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/DriverException.java @@ -0,0 +1,17 @@ +package org.python.util.install.driver; + +public class DriverException extends Exception { + + public DriverException() { + super(); + } + + public DriverException(String message) { + super(message); + } + + public DriverException(Throwable cause) { + super(cause); + } + +} diff --git a/installer/src/java/org/python/util/install/driver/GuiAutotest.java b/installer/src/java/org/python/util/install/driver/GuiAutotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/GuiAutotest.java @@ -0,0 +1,188 @@ +package org.python.util.install.driver; + +import java.awt.AWTException; +import java.awt.Robot; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.python.util.install.InstallerCommandLine; + +public class GuiAutotest extends Autotest { + + private static final int _DEFAULT_DELAY = 500; // ms + + private Robot _robot; + private List _keyActions; + private boolean _waiting = false; + + protected GuiAutotest(InstallerCommandLine commandLine) throws IOException, DriverException { + super(commandLine); + _keyActions = new ArrayList(); + // pass in the target directory, verbositiy + String[] args = new String[] { "-d", getTargetDir().getAbsolutePath() }; + setCommandLineArgs(args); + addAdditionalArguments(); + } + + protected String getNameSuffix() { + return "guiTest"; + } + + /** + * add a normal key action (press and release) + * + * @param keyCode + */ + protected void addKeyAction(int keyCode) { + KeyAction keyAction = new KeyAction(keyCode); + addKeyAction(keyAction); + } + + /** + * add a normal key action (press and release), with a specific delay + * + * @param keyCode + * @param delay + */ + protected void addKeyAction(int keyCode, int delay) { + KeyAction keyAction = new KeyAction(keyCode, delay); + addKeyAction(keyAction); + } + + /** + * add a key action (press and release) which waits before executing + * + * @param keyCode + */ + protected void addWaitingKeyAction(int keyCode) { + KeyAction keyAction = new KeyAction(keyCode, true); + addKeyAction(keyAction); + } + + protected void setWaiting(boolean waiting) { + _waiting = waiting; + } + + /** + * execute a single gui auto test + * + * @throws DriverException + */ + protected void execute() throws DriverException { + try { + _robot = new Robot(); + + System.out.println("waiting 2 seconds for the first gui ... please do not change focus"); + _robot.delay(2000); // initial gui load + + Iterator actionsIterator = _keyActions.iterator(); + while (actionsIterator.hasNext()) { + KeyAction keyAction = (KeyAction) actionsIterator.next(); + setWaiting(keyAction.isWait()); + if (isWaiting()) { + System.out.println("waiting for the installation to finish ..."); + } + while (isWaiting()) { + try { + Thread.sleep(_DEFAULT_DELAY); + } catch (InterruptedException e) { + throw new DriverException(e); + } + } + executeKeyAction(keyAction); + } + } catch (AWTException ae) { + throw new DriverException(ae); + } + + } + + /** + * General KeyAction + */ + protected static class KeyAction { + private int _keyCode; + private int _delay; + private boolean _wait; + + /** + * @param keyCode + */ + protected KeyAction(int keyCode) { + this(keyCode, _DEFAULT_DELAY); + } + + /** + * @param keyCode + * @param delay in ms + */ + protected KeyAction(int keyCode, int delay) { + super(); + setKeyCode(keyCode); + setDelay(delay); + } + + /** + * @param keyCode + * @param wait true if we should wait before executing this key action + */ + protected KeyAction(int keyCode, boolean wait) { + this(keyCode, _DEFAULT_DELAY); + setWait(wait); + } + + protected void setKeyCode(int keyCode) { + _keyCode = keyCode; + } + + protected void setDelay(int delay) { + _delay = delay; + } + + protected int getDelay() { + return _delay; + } + + protected int getKeyCode() { + return _keyCode; + } + + protected void setWait(boolean wait) { + _wait = wait; + } + + protected boolean isWait() { + return _wait; + } + } + + // + // interface InstallationListener + // + + public void progressFinished() { + setWaiting(false); + } + + // + // private stuff + // + + private boolean isWaiting() { + return _waiting; + } + + private void addKeyAction(KeyAction keyAction) { + _keyActions.add(keyAction); + } + + private void executeKeyAction(KeyAction keyAction) { + _robot.delay(keyAction.getDelay()); + _robot.keyPress(keyAction.getKeyCode()); + _robot.delay(20); // delay was handled before press + _robot.keyRelease(keyAction.getKeyCode()); + } + +} diff --git a/installer/src/java/org/python/util/install/driver/InstallationDriver.java b/installer/src/java/org/python/util/install/driver/InstallationDriver.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/InstallationDriver.java @@ -0,0 +1,416 @@ +package org.python.util.install.driver; + +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.python.util.install.ConsoleInstaller; +import org.python.util.install.Installation; +import org.python.util.install.InstallerCommandLine; +import org.python.util.install.JavaHomeHandler; + +public class InstallationDriver { + private SilentAutotest[] _silentTests; + private ConsoleAutotest[] _consoleTests; + private GuiAutotest[] _guiTests; + private InstallerCommandLine _commandLine; + + /** + * construct the driver + * + * @param commandLine the console arguments + * + * @throws IOException + * @throws DriverException + */ + public InstallationDriver(InstallerCommandLine commandLine) throws DriverException { + _commandLine = commandLine; + try { + buildSilentTests(); + buildConsoleTests(); + buildGuiTests(); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + /** + * execute all the automatic tests + * + * @throws DriverException + */ + public void drive() throws DriverException { + try { + // silent tests + for (int i = 0; i < _silentTests.length; i++) { + driveSilentTest(_silentTests[i]); + } + // console tests + for (int i = 0; i < _consoleTests.length; i++) { + driveConsoleTest(_consoleTests[i]); + } + // gui tests + for (int i = 0; i < _guiTests.length; i++) { + driveGuiTest(_guiTests[i]); + } + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + /** + * execute a single console test + * + * @param consoleTest + * @throws DriverException + * @throws IOException + * @throws IOException + */ + private void driveConsoleTest(ConsoleAutotest consoleTest) throws DriverException, IOException { + Tunnel _tunnel; + _tunnel = new Tunnel(); + // have to fork off the driver thread first + ConsoleDriver driver = new ConsoleDriver(_tunnel, consoleTest.getAnswers()); + driver.start(); + // now do the installation + Installation.driverMain(consoleTest.getCommandLineArgs(), consoleTest, _tunnel); + _tunnel.close(); + validate(consoleTest); + } + + /** + * execute a single silent test + * + * @param silentTest + * @throws DriverException + */ + private void driveSilentTest(SilentAutotest silentTest) throws DriverException { + Installation.driverMain(silentTest.getCommandLineArgs(), silentTest, null); // only a thin wrapper + validate(silentTest); + } + + /** + * execute a single gui test + * + * @param guiTest + * @throws DriverException + */ + private void driveGuiTest(GuiAutotest guiTest) throws DriverException { + Installation.driverMain(guiTest.getCommandLineArgs(), guiTest, null); // only a thin wrapper + guiTest.execute(); + validate(guiTest); + } + + /** + * perform validations after the test was run + * + * @param autoTest + * @throws DriverException + */ + private void validate(Autotest autoTest) throws DriverException { + autoTest.assertTargetDirNotEmpty(); + if (autoTest.getVerifier() != null) { + System.out.println("verifying installation - this can take a while ..."); + autoTest.getVerifier().verify(); + System.out.println("... installation ok.\n"); + } + } + + /** + * @return the command line the autotest session was started with + */ + private InstallerCommandLine getOriginalCommandLine() { + return _commandLine; + } + + /** + * build all the silent tests + * + * @throws IOException + * @throws DriverException + */ + private void buildSilentTests() throws IOException, DriverException { + List silentTests = new ArrayList(50); + + SilentAutotest test1 = new SilentAutotest(getOriginalCommandLine()); + String[] arguments = new String[] { "-s" }; + test1.setCommandLineArgs(arguments); + test1.addAdditionalArguments(); // this also adds target directory + test1.setVerifier(new NormalVerifier()); + silentTests.add(test1); + + SilentAutotest test2 = new SilentAutotest(getOriginalCommandLine()); + arguments = new String[] { "-s", "-t", "minimum" }; + test2.setCommandLineArgs(arguments); + test2.addAdditionalArguments(); + test2.setVerifier(new NormalVerifier()); + silentTests.add(test2); + + SilentAutotest test3 = new SilentAutotest(getOriginalCommandLine()); + arguments = new String[] { "-s", "-t", "standalone" }; + test3.setCommandLineArgs(arguments); + test3.addAdditionalArguments(); + test3.setVerifier(new StandaloneVerifier()); + silentTests.add(test3); + + // build array + int size = silentTests.size(); + _silentTests = new SilentAutotest[size]; + Iterator silentIterator = silentTests.iterator(); + for (int i = 0; i < size; i++) { + _silentTests[i] = silentIterator.next(); + } + } + + /** + * build all the console tests + * + * @throws IOException + * @throws DriverException + */ + private void buildConsoleTests() throws IOException, DriverException { + List consoleTests = new ArrayList(5); + final String[] arguments; + if (getOriginalCommandLine().hasVerboseOption()) { + arguments = new String[] { "-c", "-v" }; + } else { + arguments = new String[] { "-c" }; + } + // do NOT call addAdditionalArguments() + + ConsoleAutotest test1 = new ConsoleAutotest(getOriginalCommandLine()); + test1.setCommandLineArgs(arguments); + test1.addAnswer("e"); // language + test1.addAnswer("n"); // no read of license + test1.addAnswer("y"); // accept license + test1.addAnswer("3"); // type: minimum + test1.addAnswer("n"); // include: nothing + test1.addAnswer(test1.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test1); + test1.addAnswer("y"); // confirm copying + test1.addAnswer("n"); // no readme + test1.setVerifier(new NormalVerifier()); + consoleTests.add(test1); + + ConsoleAutotest test2 = new ConsoleAutotest(getOriginalCommandLine()); + test2.setCommandLineArgs(arguments); + test2.addAnswer("e"); // language + test2.addAnswer("n"); // no read of license + test2.addAnswer("y"); // accept license + test2.addAnswer("3"); // type: minimum + test2.addAnswer("y"); // include + test2.addAnswer("mod"); // include + test2.addAnswer("demo"); // include + test2.addAnswer("src"); // include + test2.addAnswer("n"); // no further includes + test2.addAnswer("y"); // exclude + test2.addAnswer("demo"); // exclude + test2.addAnswer("wrongAnswer"); // wrong answer + test2.addAnswer("n"); // no further excludes + test2.addAnswer(test2.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test2); + test2.addAnswer("y"); // confirm copying + test2.addAnswer("n"); // no readme + test2.setVerifier(new NormalVerifier()); + consoleTests.add(test2); + + ConsoleAutotest test3 = new ConsoleAutotest(getOriginalCommandLine()); + test3.setCommandLineArgs(arguments); + test3.addAnswer("e"); // language + test3.addAnswer("n"); // no read of license + test3.addAnswer("y"); // accept license + test3.addAnswer("9"); // type: standalone + test3.addAnswer(test3.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test3); + test3.addAnswer("y"); // confirm copying + test3.addAnswer("n"); // no readme + test3.setVerifier(new StandaloneVerifier()); + consoleTests.add(test3); + + // test for bug 1783960 + ConsoleAutotest test4 = new ConsoleAutotest(getOriginalCommandLine()); + test4.setCommandLineArgs(arguments); + test4.addAnswer("e"); // language + test4.addAnswer("n"); // no read of license + test4.addAnswer("y"); // accept license + test4.addAnswer("2"); // type: standard + test4.addAnswer("n"); // no includes + test4.addAnswer("y"); // exclude + test4.addAnswer("n"); // no further excludes + test4.addAnswer(test4.getTargetDir().getAbsolutePath()); // target directory + addJavaAndOSAnswers(test4); + test4.addAnswer("y"); // confirm copying + test4.addAnswer("n"); // no readme + test4.setVerifier(new NormalVerifier()); + consoleTests.add(test4); + + // build array + int size = consoleTests.size(); + _consoleTests = new ConsoleAutotest[size]; + Iterator consoleIterator = consoleTests.iterator(); + for (int i = 0; i < size; i++) { + _consoleTests[i] = consoleIterator.next(); + } + } + + private void addJavaAndOSAnswers(ConsoleAutotest test) { + JavaHomeHandler javaHomeHandler = test.getJavaHomeHandler(); + if (javaHomeHandler.isDeviation() && javaHomeHandler.isValidHome()) { + test.addAnswer(javaHomeHandler.getHome().getAbsolutePath()); // different jre + } else { + test.addAnswer(ConsoleInstaller.CURRENT_JRE); + } + if (!Installation.isValidOs()) { + test.addAnswer(""); // enter to proceed anyway + } + } + + /** + * build all the gui tests + * + * @throws IOException + * @throws DriverException + */ + private void buildGuiTests() throws IOException, DriverException { + List guiTests = new ArrayList(50); + + if (Installation.isGuiAllowed()) { + GuiAutotest guiTest1 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest1); + // type page - use 'Standard' + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_TAB); + guiTest1.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest1); + guiTest1.setVerifier(new NormalVerifier()); + guiTests.add(guiTest1); + + GuiAutotest guiTest2 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest2); + // type page - use 'All' + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_SPACE); // select 'All' + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_TAB); + guiTest2.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest2); + guiTest2.setVerifier(new NormalVerifier()); + guiTests.add(guiTest2); + + GuiAutotest guiTest3 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest3); + // type page - use 'Custom' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // select 'Custom' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // deselect 'Demos and Examples' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // deselect 'Documentation' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); // select 'Sources' + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_TAB); + guiTest3.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest3); + guiTest3.setVerifier(new NormalVerifier()); + guiTests.add(guiTest3); + + GuiAutotest guiTest4 = new GuiAutotest(getOriginalCommandLine()); + buildLanguageAndLicensePage(guiTest4); + // type page - use 'Standalone' + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_SPACE); // select 'Standalone' + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_TAB); + guiTest4.addKeyAction(KeyEvent.VK_SPACE); + buildRestOfGuiPages(guiTest4); + guiTest4.setVerifier(new StandaloneVerifier()); + guiTests.add(guiTest4); + } + + // build array + int size = guiTests.size(); + _guiTests = new GuiAutotest[size]; + Iterator guiIterator = guiTests.iterator(); + for (int i = 0; i < size; i++) { + _guiTests[i] = guiIterator.next(); + } + } + + private void buildLanguageAndLicensePage(GuiAutotest guiTest) { + // language page + guiTest.addKeyAction(KeyEvent.VK_E); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // license page + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); // select "i accept" + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); + } + + private void buildRestOfGuiPages(GuiAutotest guiTest) { + // directory page + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // java selection page + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + JavaHomeHandler javaHomeHandler = guiTest.getJavaHomeHandler(); + boolean isValidDeviation = javaHomeHandler.isDeviation() && javaHomeHandler.isValidHome(); + if (isValidDeviation) { // need 2 more tabs + guiTest.addKeyAction(KeyEvent.VK_TAB); + guiTest.addKeyAction(KeyEvent.VK_TAB); + } + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // overview page + if (isValidDeviation) { + guiTest.addKeyAction(KeyEvent.VK_SPACE, 3000); // enough time to check the java version + } else { + guiTest.addKeyAction(KeyEvent.VK_SPACE); + } + // installation page (skipped) + // readme page + guiTest.addWaitingKeyAction(KeyEvent.VK_TAB); // wait for the installation to finish + guiTest.addKeyAction(KeyEvent.VK_SPACE); + // success page + guiTest.addKeyAction(KeyEvent.VK_SPACE); + } +} diff --git a/installer/src/java/org/python/util/install/driver/NormalVerifier.java b/installer/src/java/org/python/util/install/driver/NormalVerifier.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/NormalVerifier.java @@ -0,0 +1,287 @@ +package org.python.util.install.driver; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.text.MessageFormat; +import java.util.StringTokenizer; + +import org.python.util.install.ChildProcess; +import org.python.util.install.FileHelper; +import org.python.util.install.Installation; +import org.python.util.install.JavaHomeHandler; + +public class NormalVerifier implements Verifier { + + protected static final String AUTOTEST_PY = "autotest.py"; + + protected static final String JYTHON_TEST = "jython_test"; + + private static final String BIN = "bin"; + + private static final String BAT_EXTENSION = ".bat"; + + private static final String JYTHON_UP = "jython up and running!"; + + private static final String JYTHON = "jython"; + + private static final String TEMPLATE_SUFFIX = ".template"; + + private static final String VERIFYING = "verifying"; + + private File _targetDir; + + public void setTargetDir(File targetDir) { + _targetDir = targetDir; + } + + public File getTargetDir() { + return _targetDir; + } + + public void verify() throws DriverException { + createTestScriptFile(); // create the test .py script + // verify the most simple start of jython works + verifyStart(getSimpleCommand()); + if (doShellScriptTests()) { + // verify more complex versions of starting jython + verifyStart(getShellScriptTestCommand()); + } + } + + /** + * Will be overridden in subclass StandaloneVerifier + * + * @return the command array to start jython with + * @throws DriverException + * if there was a problem getting the target directory path + */ + protected String[] getSimpleCommand() throws DriverException { + String parentDirName = null; + try { + parentDirName = getTargetDir().getCanonicalPath() + File.separator; + } catch (IOException ioe) { + throw new DriverException(ioe); + } + String[] command = new String[2]; + if (Installation.isWindows()) { + command[0] = parentDirName + JYTHON + BAT_EXTENSION; + } else { + command[0] = parentDirName + JYTHON; + } + command[1] = parentDirName + AUTOTEST_PY; + return command; + } + + /** + * @return The command to test the shell script more deeply + * @throws DriverException + */ + protected final String[] getShellScriptTestCommand() throws DriverException { + // first we have to create the shell script + File testCommandDir = getShellScriptTestCommandDir(); + if (!testCommandDir.exists()) { + if (!testCommandDir.mkdirs()) { + throw new DriverException("unable to create directory " + + testCommandDir.getAbsolutePath()); + } + } + String commandName = JYTHON_TEST; + boolean isWindows = Installation.isWindows(); + if (isWindows) { + commandName = commandName.concat(BAT_EXTENSION); + } + File command = new File(testCommandDir, commandName); + try { + if (!command.exists()) { + command.createNewFile(); + } + FileHelper.write(command, getShellScriptTestContents()); + if (!isWindows) { + FileHelper.makeExecutable(command); + } + return new String[] {command.getCanonicalPath()}; + } catch (Exception e) { + throw new DriverException(e); + } + } + + /** + * @return The contents of the shell test script + * @throws DriverException + */ + protected final String getShellScriptTestContents() throws DriverException { + String contents = ""; + String templateName = JYTHON_TEST; + if (Installation.isWindows()) { + templateName = templateName.concat(BAT_EXTENSION); + } + templateName = templateName.concat(TEMPLATE_SUFFIX); + InputStream inputStream = FileHelper.getRelativeURLAsStream(getClass(), templateName); + if (inputStream != null) { + try { + String template = FileHelper.readAll(inputStream); + String targetDirPath = getTargetDir().getCanonicalPath(); + String upScriptPath = getSimpleCommand()[1]; + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(); + String javaHomeString = ""; + if (javaHomeHandler.isValidHome()) { + javaHomeString = javaHomeHandler.getHome().getAbsolutePath(); + } + contents = MessageFormat.format(template, + targetDirPath, + upScriptPath, + javaHomeString, + VERIFYING); + } catch (Exception e) { + throw new DriverException(e); + } + } + return contents; + } + + /** + * @return The directory where to create the shell script test command in. + * + * @throws DriverException + */ + protected final File getShellScriptTestCommandDir() throws DriverException { + String dirName; + try { + dirName = getTargetDir().getCanonicalPath().concat(File.separator).concat(BIN); + return new File(dirName); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + /** + * Internal method verifying a jython-starting command by capturing the ouptut + * + * @param command + * + * @throws DriverException + */ + private void verifyStart(String[] command) throws DriverException { + ChildProcess childProcess = new ChildProcess(command); + childProcess.setDebug(true); + ByteArrayOutputStream redirectedErr = new ByteArrayOutputStream(); + ByteArrayOutputStream redirectedOut = new ByteArrayOutputStream(); + int exitValue = 0; + PrintStream oldErr = System.err; + PrintStream oldOut = System.out; + try { + System.setErr(new PrintStream(redirectedErr)); + System.setOut(new PrintStream(redirectedOut)); + exitValue = childProcess.run(); + } finally { + System.setErr(oldErr); + System.setOut(oldOut); + } + // verify the output + String output = null; + String error = null; + try { + redirectedErr.flush(); + redirectedOut.flush(); + String encoding = "US-ASCII"; + output = redirectedOut.toString(encoding); + error = redirectedErr.toString(encoding); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + if (exitValue != 0) { + throw new DriverException("start of jython failed, output:\n" + output + "\nerror:\n" + + error); + } + verifyError(error); + verifyOutput(output); + } + + /** + * Will be overridden in subclass StandaloneVerifier + * + * @return true if the jython start shell script should be verified (using + * different options) + */ + protected boolean doShellScriptTests() { + return true; + } + + private void verifyError(String error) throws DriverException { + StringTokenizer tokenizer = new StringTokenizer(error, "\n"); + while (tokenizer.hasMoreTokens()) { + String line = tokenizer.nextToken(); + if (isExpectedError(line)) { + feedback(line); + } else { + throw new DriverException(error); + } + } + } + + private boolean isExpectedError(String line) { + boolean expected = false; + if (line.startsWith("*sys-package-mgr*")) { + expected = true; + } else if (line.indexOf("32 bit") >= 0 && line.indexOf("64 bit") >= 0) { + // OS X incompatibility message when using -A -j java1.6.0 from java1.5.0 + expected = true; + } + return expected; + } + + private void verifyOutput(String output) throws DriverException { + boolean started = false; + StringTokenizer tokenizer = new StringTokenizer(output, "\n"); + while (tokenizer.hasMoreTokens()) { + String line = tokenizer.nextToken(); + if (isExpectedOutput(line)) { + feedback(line); + if (line.startsWith(JYTHON_UP)) { + started = true; + } + } else { + throw new DriverException(output); + } + } + if (!started) { + throw new DriverException("start of jython failed:\n" + output); + } + } + + private boolean isExpectedOutput(String line) { + boolean expected = false; + if (line.startsWith("[ChildProcess]") || line.startsWith(VERIFYING)) { + expected = true; + } else if (line.startsWith(JYTHON_UP)) { + expected = true; + } + return expected; + } + + private String getTestScript() { + StringBuilder b = new StringBuilder(80); + b.append("import sys\n"); + b.append("import os\n"); + b.append("print '"); + b.append(JYTHON_UP); + b.append("'\n"); + return b.toString(); + } + + private void createTestScriptFile() throws DriverException { + File file = new File(getTargetDir(), AUTOTEST_PY); + try { + FileHelper.write(file, getTestScript()); + } catch (IOException ioe) { + throw new DriverException(ioe); + } + } + + private void feedback(String line) { + System.out.println(line); + } +} diff --git a/installer/src/java/org/python/util/install/driver/SilentAutotest.java b/installer/src/java/org/python/util/install/driver/SilentAutotest.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/SilentAutotest.java @@ -0,0 +1,25 @@ +package org.python.util.install.driver; + +import java.io.IOException; + +import org.python.util.install.InstallerCommandLine; + +public class SilentAutotest extends Autotest { + + protected SilentAutotest(InstallerCommandLine commandLine) throws IOException, DriverException { + super(commandLine); + } + + protected String getNameSuffix() { + return "silentTest"; + } + + // + // interface InstallationListener + // + + public void progressFinished() { + // ignored + } + +} diff --git a/installer/src/java/org/python/util/install/driver/StandaloneVerifier.java b/installer/src/java/org/python/util/install/driver/StandaloneVerifier.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/StandaloneVerifier.java @@ -0,0 +1,74 @@ +package org.python.util.install.driver; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.python.util.install.JavaHomeHandler; + +public class StandaloneVerifier extends NormalVerifier { + + public void verify() throws DriverException { + // make sure only JYTHON_JAR is in the target directory + if (getTargetDir().listFiles().length > 1) { + throw new DriverException("more than " + JYTHON_JAR + " installed"); + } + // make sure JYTHON_JAR contains a MANIFEST and a /Lib directory + verifyJythonJar(); + // do the jython startup verification from the superclass + super.verify(); + } + + @Override + protected String[] getSimpleCommand() throws DriverException { + String parentDirName = null; + try { + parentDirName = getTargetDir().getCanonicalPath() + File.separator; + } catch (IOException ioe) { + throw new DriverException(ioe); + } + String command[] = new String[4]; + command[0] = new JavaHomeHandler().getExecutableName(); + command[1] = "-jar"; + command[2] = parentDirName + JYTHON_JAR; + command[3] = parentDirName + AUTOTEST_PY; + return command; + } + + @Override + protected boolean doShellScriptTests() { + return false; + } + + private void verifyJythonJar() throws DriverException { + File jythonJar = getTargetDir().listFiles()[0]; + JarFile jar = null; + try { + jar = new JarFile(jythonJar); + if (jar.getManifest() == null) { + throw new DriverException(JYTHON_JAR + " contains no MANIFEST"); + } + boolean hasLibDir = false; + Enumeration entries = jar.entries(); + while (!hasLibDir && entries.hasMoreElements()) { + JarEntry entry = (JarEntry)entries.nextElement(); + if (entry.getName().startsWith("Lib/")) { + hasLibDir = true; + } + } + if (!hasLibDir) { + throw new DriverException(JYTHON_JAR + " contains no /Lib directory"); + } + } catch (IOException e) { + throw new DriverException(e); + } finally { + if (jar != null) { + try { + jar.close(); + } catch (IOException ioe) {} + } + } + } +} diff --git a/installer/src/java/org/python/util/install/driver/Tunnel.java b/installer/src/java/org/python/util/install/driver/Tunnel.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/Tunnel.java @@ -0,0 +1,61 @@ +package org.python.util.install.driver; + +import java.io.IOException; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +/** + * A communication tunnel between a console driver and a console. + * + *
+ *   (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+ *   (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+ * 
+ */ +public class Tunnel { + + public static final String NEW_LINE = "\n"; + + private PipedOutputStream _questionSenderStream; + private PipedInputStream _questionReceiverStream; + private PipedOutputStream _answerSenderStream; + private PipedInputStream _answerReceiverStream; + + public Tunnel() throws IOException { + _questionSenderStream = new PipedOutputStream(); + _questionReceiverStream = new PipedInputStream(); + _questionSenderStream.connect(_questionReceiverStream); + + _answerSenderStream = new PipedOutputStream(); + _answerReceiverStream = new PipedInputStream(); + _answerSenderStream.connect(_answerReceiverStream); + } + + public PipedOutputStream getQuestionSenderStream() { + return _questionSenderStream; + } + + public PipedInputStream getQuestionReceiverStream() { + return _questionReceiverStream; + } + + public PipedOutputStream getAnswerSenderStream() { + return _answerSenderStream; + } + + public PipedInputStream getAnswerReceiverStream() { + return _answerReceiverStream; + } + + public void close() throws IOException { + _questionReceiverStream.close(); + _questionSenderStream.close(); + _answerReceiverStream.close(); + _answerSenderStream.close(); + + _questionReceiverStream = null; + _questionSenderStream = null; + _answerReceiverStream = null; + _answerSenderStream = null; + } +} diff --git a/installer/src/java/org/python/util/install/driver/Verifier.java b/installer/src/java/org/python/util/install/driver/Verifier.java new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/Verifier.java @@ -0,0 +1,17 @@ +package org.python.util.install.driver; + +import java.io.File; + +import org.python.util.install.JarInstaller; + +public interface Verifier { + + public static final String JYTHON_JAR = JarInstaller.JYTHON_JAR; + + public void setTargetDir(File targetDir); + + public File getTargetDir(); + + public void verify() throws DriverException; + +} diff --git a/installer/src/java/org/python/util/install/driver/jython_test.bat.template b/installer/src/java/org/python/util/install/driver/jython_test.bat.template new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/jython_test.bat.template @@ -0,0 +1,73 @@ + at echo off + +rem 3 variables to be set from the caller, UNquoted: +set _INSTALL_DIR={0} +set _SCRIPT={1} +set _JAVA_HOME={2} + +rem save old home env vars and classpath: +set _OLD_JAVA_HOME=%JAVA_HOME% +set _OLD_JYTHON_HOME=%JYTHON_HOME% +set _OLD_CLASSPATH=%CLASSPATH% + +cd /d "%_INSTALL_DIR%\bin" + +echo {3}: only JAVA_HOME, quoted: +set JAVA_HOME="%_JAVA_HOME%" +set JYTHON_HOME= +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: only JAVA_HOME, UNquoted: +set JAVA_HOME=%_JAVA_HOME% +set JYTHON_HOME= +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: both homes quoted: +set JAVA_HOME="%_JAVA_HOME%" +set JYTHON_HOME="%_INSTALL_DIR%" +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: both homes UNquoted: +set JAVA_HOME=%_JAVA_HOME% +set JYTHON_HOME=%_INSTALL_DIR% +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +cd .. + +echo {3}: no home, calling in home dir: +set JAVA_HOME= +set JYTHON_HOME= +call jython.bat "%_SCRIPT%" +set E=%ERRORLEVEL% + +cd .. + +echo {3}: no home, calling /jython.bat from another working dir:" +set JAVA_HOME= +set JYTHON_HOME= +call "%_INSTALL_DIR%\jython.bat" "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: no home, calling bin/jython.bat from another working dir:" +set JAVA_HOME= +set JYTHON_HOME= +call "%_INSTALL_DIR%\bin\jython.bat" "%_SCRIPT%" +set E=%ERRORLEVEL% + +echo {3}: no home, setting CLASSPATH, calling /jython.bat from another working dir:" +set JAVA_HOME= +set JYTHON_HOME= +set CLASSPATH=.;C:\Program Files (x86)\Java\jre6\lib\ext\QTJava.zip +call "%_INSTALL_DIR%\jython.bat" "%_SCRIPT%" +set E=%ERRORLEVEL% + +rem cleanup: +set JAVA_HOME=%_OLD_JAVA_HOME% +set JYTHON_HOME=%_OLD_JYTHON_HOME% +set CLASSPATH=%_OLD_CLASSPATH% +cd /d "%~dp0%" +exit /b %E% diff --git a/installer/src/java/org/python/util/install/driver/jython_test.template b/installer/src/java/org/python/util/install/driver/jython_test.template new file mode 100644 --- /dev/null +++ b/installer/src/java/org/python/util/install/driver/jython_test.template @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +# 3 variables to be set from the caller (unquoted) -> quoted in here: +_INSTALL_DIR="{0}" +_SCRIPT="{1}" +_JAVA_HOME="{2}" + +# save old home env vars: +_OLD_JAVA_HOME=$JAVA_HOME +_OLD_JYTHON_HOME=$JYTHON_HOME +# save current dir +_CURDIR=`pwd` + +cd "$_INSTALL_DIR/bin" + +echo "{3}: no home:" +export JAVA_HOME= +export JYTHON_HOME= +./jython "$_SCRIPT" + +echo "{3}: only JAVA_HOME:" +export JAVA_HOME="$_JAVA_HOME" +export JYTHON_HOME= +./jython "$_SCRIPT" + +echo "{3}: only JYTHON_HOME:" +export JAVA_HOME= +export JYTHON_HOME="$_INSTALL_DIR" +./jython "$_SCRIPT" + +echo "{3}: both homes:" +export JAVA_HOME="$_JAVA_HOME" +export JYTHON_HOME="$_INSTALL_DIR" +./jython "$_SCRIPT" + +cd .. + +echo "{3}: no home, calling in home dir:" +export JAVA_HOME= +export JYTHON_HOME= +./jython "$_SCRIPT" + +cd ~ + +echo "{3}: no home, calling /jython from another working dir:" +export JAVA_HOME= +export JYTHON_HOME= +"$_INSTALL_DIR/jython" "$_SCRIPT" + +echo "{3}: no home, calling /bin/jython from another working dir:" +export JAVA_HOME= +export JYTHON_HOME= +"$_INSTALL_DIR/bin/jython" "$_SCRIPT" + +# cleanup: +cd "$_CURDIR" +export JAVA_HOME=$_OLD_JAVA_HOME +export JYTHON_HOME=$_OLD_JYTHON_HOME diff --git a/installer/src/java/org/python/util/install/jython_small_c.png b/installer/src/java/org/python/util/install/jython_small_c.png new file mode 100644 index 0000000000000000000000000000000000000000..e65a7f18e5ecad694dd2093cf01256a2eb93aada GIT binary patch [stripped] diff --git a/installer/test/java/org/AllTests.java b/installer/test/java/org/AllTests.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/AllTests.java @@ -0,0 +1,101 @@ +package org; + +import java.io.File; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * A package recursive test suite. + *

+ * All classes ending with 'Test' are added to the suite. + * + * @see AllTests.TestClassFilter + */ +public class AllTests extends TestSuite { + + /** + * @return Test suite at the directory where this class resists + * + * @throws Exception + */ + public static Test suite() throws Exception { + Class suiteClass = AllTests.class; + String testSuiteClassName = suiteClass.getName(); + File suiteFile = new File(suiteClass.getClassLoader().getResource( + testSuiteClassName.replace('.', '/').concat(".class")).getFile()); + String basePackage = suiteClass.getPackage().getName(); + File baseDir = suiteFile.getParentFile(); + TestSuite suite = new TestSuite("Test " + basePackage + " recursive."); + buildSuite(baseDir.getAbsolutePath().length(), basePackage, baseDir, new TestClassFilter(), suite); + return suite; + } + + // + // private methods + // + + private static void buildSuite(int prefixLength, String basePackage, File currentDir, FilenameFilter filter, + TestSuite currentSuite) throws Exception { + List potentialDirectories = Arrays.asList(currentDir.listFiles(filter)); + if (potentialDirectories.size() == 0) { + return; + } + StringBuffer currentPackageName = new StringBuffer(200); + currentPackageName.append(basePackage); + currentPackageName.append(currentDir.getAbsolutePath().substring(prefixLength).replace('\\', '.').replace('/', + '.')); + + List classFiles = new ArrayList(potentialDirectories.size()); + Collections.sort(potentialDirectories, new FileComparator()); + Iterator directoryIterator = potentialDirectories.iterator(); + while (directoryIterator.hasNext()) { + File potentialDirectory = (File) directoryIterator.next(); + if (potentialDirectory.isDirectory()) { + TestSuite subTestSuite = new TestSuite(potentialDirectory.getName()); + buildSuite(prefixLength, basePackage, potentialDirectory, filter, subTestSuite); + // only if suite contains tests + if (subTestSuite.countTestCases() > 0) { + currentSuite.addTest(subTestSuite); + } + } else { + classFiles.add(potentialDirectory); + } + } + Iterator fileIterator = classFiles.iterator(); + while (fileIterator.hasNext()) { + File file = (File) fileIterator.next(); + StringBuffer className = new StringBuffer(200); + className.append(currentPackageName); + className.append('.'); + String fileName = file.getName(); + className.append(fileName); + className.setLength(className.length() - 6); + currentSuite.addTest(new TestSuite(Class.forName(className.toString()), fileName.substring(0, fileName + .length() - 6))); + } + } + + private static class TestClassFilter implements FilenameFilter { + public boolean accept(File dir, String name) { + if (name.endsWith("Test.class")) { + return true; + } + return new File(dir, name).isDirectory(); + } + } + + private static class FileComparator implements Comparator { + public int compare(File f1, File f2) { + return f1.getAbsolutePath().compareTo(f2.getAbsolutePath()); + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/ApplicationTest.java b/installer/test/java/org/apache/commons/cli/ApplicationTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ApplicationTest.java @@ -0,0 +1,120 @@ +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + *

+ * This is a collection of tests that test real world + * applications command lines. + *

+ * + *

+ * The following are the applications that are tested: + *

    + *
  • Ant
  • + *
+ *

+ * + * @author John Keyes (john at integralsource.com) + */ +public class ApplicationTest extends TestCase { + + public static Test suite() { + return new TestSuite(ApplicationTest.class); + } + + public ApplicationTest(String name) + { + super(name); + } + + /** + * + */ + public void testLs() { + // create the command line parser + CommandLineParser parser = new PosixParser(); + Options options = new Options(); + options.addOption( "a", "all", false, "do not hide entries starting with ." ); + options.addOption( "A", "almost-all", false, "do not list implied . and .." ); + options.addOption( "b", "escape", false, "print octal escapes for nongraphic characters" ); + OptionBuilder.withLongOpt( "block-size" ); + OptionBuilder.withDescription( "use SIZE-byte blocks" ); + OptionBuilder.withValueSeparator( '=' ); + OptionBuilder.hasArg(); + options.addOption(OptionBuilder.create()); +// options.addOption( OptionBuilder.withLongOpt( "block-size" ) +// .withDescription( "use SIZE-byte blocks" ) +// .withValueSeparator( '=' ) +// .hasArg() +// .create() ); + options.addOption( "B", "ignore-backups", false, "do not list implied entried ending with ~"); + options.addOption( "c", false, "with -lt: sort by, and show, ctime (time of last modification of file status information) with -l:show ctime and sort by name otherwise: sort by ctime" ); + options.addOption( "C", false, "list entries by columns" ); + + String[] args = new String[]{ "--block-size=10" }; + + try { + CommandLine line = parser.parse( options, args ); + assertTrue( line.hasOption( "block-size" ) ); + assertEquals( line.getOptionValue( "block-size" ), "10" ); + } + catch( ParseException exp ) { + fail( "Unexpected exception:" + exp.getMessage() ); + } + } + + /** + * Ant test + */ + public void testAnt() { + // use the GNU parser + CommandLineParser parser = new GnuParser( ); + Options options = new Options(); + options.addOption( "help", false, "print this message" ); + options.addOption( "projecthelp", false, "print project help information" ); + options.addOption( "version", false, "print the version information and exit" ); + options.addOption( "quiet", false, "be extra quiet" ); + options.addOption( "verbose", false, "be extra verbose" ); + options.addOption( "debug", false, "print debug information" ); + options.addOption( "version", false, "produce logging information without adornments" ); + options.addOption( "logfile", true, "use given file for log" ); + options.addOption( "logger", true, "the class which is to perform the logging" ); + options.addOption( "listener", true, "add an instance of a class as a project listener" ); + options.addOption( "buildfile", true, "use given buildfile" ); + OptionBuilder.withDescription( "use value for given property" ); + OptionBuilder.hasArgs(); + OptionBuilder.withValueSeparator(); + options.addOption( OptionBuilder.create( 'D' ) ); + //, null, true, , false, true ); + options.addOption( "find", true, "search for buildfile towards the root of the filesystem and use it" ); + + String[] args = new String[]{ "-buildfile", "mybuild.xml", + "-Dproperty=value", "-Dproperty1=value1", + "-projecthelp" }; + + try { + CommandLine line = parser.parse( options, args ); + + // check multiple values + String[] opts = line.getOptionValues( "D" ); + assertEquals( "property", opts[0] ); + assertEquals( "value", opts[1] ); + assertEquals( "property1", opts[2] ); + assertEquals( "value1", opts[3] ); + + // check single value + assertEquals( line.getOptionValue( "buildfile"), "mybuild.xml" ); + + // check option + assertTrue( line.hasOption( "projecthelp") ); + } + catch( ParseException exp ) { + fail( "Unexpected exception:" + exp.getMessage() ); + } + + } + +} \ No newline at end of file diff --git a/installer/test/java/org/apache/commons/cli/BugsTest.java b/installer/test/java/org/apache/commons/cli/BugsTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/BugsTest.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: BugsTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class BugsTest extends TestCase +{ + + public static Test suite() { + return new TestSuite( BugsTest.class ); + } + + public BugsTest( String name ) + { + super( name ); + } + + public void setUp() + { + } + + public void tearDown() + { + } + + public void test11457() { + Options options = new Options(); + OptionBuilder.withLongOpt( "verbose" ); + options.addOption( OptionBuilder.create() ); + String[] args = new String[] { "--verbose" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + assertTrue( cmd.hasOption( "verbose" ) ); + } + catch( ParseException exp ) { + exp.printStackTrace(); + fail( "Unexpected Exception: " + exp.getMessage() ); + } + } + + public void test11458() + { + Options options = new Options(); + OptionBuilder.withValueSeparator( '=' ); + OptionBuilder.hasArgs(); + options.addOption(OptionBuilder.create( 'D' ) ); + OptionBuilder.withValueSeparator( ':' ); + OptionBuilder.hasArgs(); + options.addOption( OptionBuilder.create( 'p' ) ); + String[] args = new String[] { "-DJAVA_HOME=/opt/java" , + "-pfile1:file2:file3" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + + String[] values = cmd.getOptionValues( 'D' ); + + assertEquals( values[0], "JAVA_HOME" ); + assertEquals( values[1], "/opt/java" ); + + values = cmd.getOptionValues( 'p' ); + + assertEquals( values[0], "file1" ); + assertEquals( values[1], "file2" ); + assertEquals( values[2], "file3" ); + + java.util.Iterator iter = cmd.iterator(); + while( iter.hasNext() ) { + Option opt = (Option)iter.next(); + switch( opt.getId() ) { + case 'D': + assertEquals( opt.getValue( 0 ), "JAVA_HOME" ); + assertEquals( opt.getValue( 1 ), "/opt/java" ); + break; + case 'p': + assertEquals( opt.getValue( 0 ), "file1" ); + assertEquals( opt.getValue( 1 ), "file2" ); + assertEquals( opt.getValue( 2 ), "file3" ); + break; + default: + fail( "-D option not found" ); + } + } + } + catch( ParseException exp ) { + fail( "Unexpected Exception:\nMessage:" + exp.getMessage() + + "Type: " + exp.getClass().getName() ); + } + } + + public void test11680() + { + Options options = new Options(); + options.addOption("f", true, "foobar"); + options.addOption("m", true, "missing"); + String[] args = new String[] { "-f" , "foo" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + + try { + cmd.getOptionValue( "f", "default f"); + cmd.getOptionValue( "m", "default m"); + } + catch( NullPointerException exp ) { + fail( "NullPointer caught: " + exp.getMessage() ); + } + } + catch( ParseException exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + } + + public void test11456() + { + // Posix + Options options = new Options(); + OptionBuilder.hasOptionalArg(); + options.addOption( OptionBuilder.create( 'a' ) ); + OptionBuilder.hasArg(); + options.addOption( OptionBuilder.create( 'b' ) ); + String[] args = new String[] { "-a", "-bvalue" }; + + CommandLineParser parser = new PosixParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + assertEquals( cmd.getOptionValue( 'b' ), "value" ); + } + catch( ParseException exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + + // GNU + options = new Options(); + OptionBuilder.hasOptionalArg(); + options.addOption( OptionBuilder.create( 'a' ) ); + OptionBuilder.hasArg(); + options.addOption( OptionBuilder.create( 'b' ) ); + args = new String[] { "-a", "-b", "value" }; + + parser = new GnuParser(); + + try { + CommandLine cmd = parser.parse( options, args ); + assertEquals( cmd.getOptionValue( 'b' ), "value" ); + } + catch( ParseException exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + + } + + public void test12210() { + // create the main options object which will handle the first parameter + Options mainOptions = new Options(); + // There can be 2 main exclusive options: -exec|-rep + + // Therefore, place them in an option group + + String[] argv = new String[] { "-exec", "-exec_opt1", "-exec_opt2" }; + OptionGroup grp = new OptionGroup(); + + grp.addOption(new Option("exec",false,"description for this option")); + + grp.addOption(new Option("rep",false,"description for this option")); + + mainOptions.addOptionGroup(grp); + + // for the exec option, there are 2 options... + Options execOptions = new Options(); + execOptions.addOption("exec_opt1",false," desc"); + execOptions.addOption("exec_opt2",false," desc"); + + // similarly, for rep there are 2 options... + Options repOptions = new Options(); + repOptions.addOption("repopto",false,"desc"); + repOptions.addOption("repoptt",false,"desc"); + + // create the parser + GnuParser parser = new GnuParser(); + + // finally, parse the arguments: + + // first parse the main options to see what the user has specified + // We set stopAtNonOption to true so it does not touch the remaining + // options + try { + CommandLine cmd = parser.parse(mainOptions,argv,true); + // get the remaining options... + argv = cmd.getArgs(); + + if(cmd.hasOption("exec")){ + cmd = parser.parse(execOptions,argv,false); + // process the exec_op1 and exec_opt2... + assertTrue( cmd.hasOption("exec_opt1") ); + assertTrue( cmd.hasOption("exec_opt2") ); + } + else if(cmd.hasOption("rep")){ + cmd = parser.parse(repOptions,argv,false); + // process the rep_op1 and rep_opt2... + } + else { + fail( "exec option not found" ); + } + } + catch( ParseException exp ) { + fail( "Unexpected exception: " + exp.getMessage() ); + } + } + + public void test13425() { + Options options = new Options(); + OptionBuilder.withLongOpt( "old-password" ); + OptionBuilder.withDescription( "Use this option to specify the old password" ); + OptionBuilder.hasArg(); + Option oldpass = OptionBuilder.create( 'o' ); + OptionBuilder.withLongOpt( "new-password" ); + OptionBuilder.withDescription( "Use this option to specify the new password" ); + OptionBuilder.hasArg(); + Option newpass = OptionBuilder.create( 'n' ); + + String[] args = { + "-o", + "-n", + "newpassword" + }; + + options.addOption( oldpass ); + options.addOption( newpass ); + + Parser parser = new PosixParser(); + + CommandLine line = null; + try { + line = parser.parse( options, args ); + } + // catch the exception and leave the method + catch( Exception exp ) { + assertTrue( exp != null ); + return; + } + fail( "MissingArgumentException not caught." + line); + } + + public void test13666() { + Options options = new Options(); + OptionBuilder.withDescription( "dir" ); + OptionBuilder.hasArg(); + Option dir = OptionBuilder.create( 'd' ); + options.addOption( dir ); + try { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp( "dir", options ); + } + catch( Exception exp ) { + fail( "Unexpected Exception: " + exp.getMessage() ); + } + } + + public void test13935() { + OptionGroup directions = new OptionGroup(); + + Option left = new Option( "l", "left", false, "go left" ); + Option right = new Option( "r", "right", false, "go right" ); + Option straight = new Option( "s", "straight", false, "go straight" ); + Option forward = new Option( "f", "forward", false, "go forward" ); + forward.setRequired( true ); + + directions.addOption( left ); + directions.addOption( right ); + directions.setRequired( true ); + + Options opts = new Options(); + opts.addOptionGroup( directions ); + opts.addOption( straight ); + + CommandLineParser parser = new PosixParser(); + boolean exception = false; + + CommandLine line = null; + String[] args = new String[] { }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + exception = true; + } + + if( !exception ) { + fail( "Expected exception not caught."); + } + + exception = false; + + args = new String[] { "-s" }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + exception = true; + } + + if( !exception ) { + fail( "Expected exception not caught."); + } + + exception = false; + + args = new String[] { "-s", "-l" }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + fail( "Unexpected exception: " + exp.getMessage() ); + } + + opts.addOption( forward ); + args = new String[] { "-s", "-l", "-f" }; + try { + line = parser.parse( opts, args ); + } + catch( ParseException exp ) { + fail( "Unexpected exception: " + exp.getMessage() ); + } + if(line != null) { + line = null; + } + } +} diff --git a/installer/test/java/org/apache/commons/cli/BuildTest.java b/installer/test/java/org/apache/commons/cli/BuildTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/BuildTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: BuildTest.java 2662 2006-02-18 14:20:33Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class BuildTest extends TestCase +{ + + public static Test suite() { + return new TestSuite(BuildTest.class); + } + + public BuildTest(String name) + { + super(name); + } + + public void setUp() + { + + } + + public void tearDown() + { + + } + + public void testSimple() + { + Options opts = new Options(); + + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("b", + true, + "toggle -b"); + } + + public void testDuplicateSimple() + { + Options opts = new Options(); + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("a", + true, + "toggle -a*"); + + assertEquals( "last one in wins", "toggle -a*", opts.getOption("a").getDescription() ); + } + + public void testLong() + { + Options opts = new Options(); + + opts.addOption("a", + "--a", + false, + "toggle -a"); + + opts.addOption("b", + "--b", + true, + "set -b"); + + } + + public void testDuplicateLong() + { + Options opts = new Options(); + opts.addOption("a", + "--a", + false, + "toggle -a"); + + opts.addOption("a", + "--a", + false, + "toggle -a*"); + assertEquals( "last one in wins", "toggle -a*", opts.getOption("a").getDescription() ); + } +} diff --git a/installer/test/java/org/apache/commons/cli/GnuParseTest.java b/installer/test/java/org/apache/commons/cli/GnuParseTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/GnuParseTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: GnuParseTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class GnuParseTest extends TestCase +{ + private Options _options = null; + private CommandLineParser _parser = null; + + public static Test suite() { + return new TestSuite( GnuParseTest.class ); + } + + public GnuParseTest( String name ) + { + super( name ); + } + + public void setUp() + { + _options = new Options() + .addOption("a", + "enable-a", + false, + "turn [a] on or off") + .addOption("b", + "bfile", + true, + "set the value of [b]") + .addOption("c", + "copt", + false, + "turn [c] on or off"); + + _parser = new GnuParser( ); + } + + public void tearDown() + { + + } + + public void testSimpleShort() + { + String[] args = new String[] { "-a", + "-b", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSimpleLong() + { + String[] args = new String[] { "--enable-a", + "--bfile", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testExtraOption() + { + String[] args = new String[] { "-a", "-d", "-b", "toast", + "foo", "bar" }; + + boolean caught = false; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 3); + } + catch (UnrecognizedOptionException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + assertTrue( "Confirm UnrecognizedOptionException caught", caught ); + } + + public void testMissingArg() + { + + String[] args = new String[] { "-b" }; + + boolean caught = false; + + CommandLine cl = null; + try + { + cl = _parser.parse(_options, args); + } + catch (MissingArgumentException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + + assertTrue( "Confirm MissingArgumentException caught " + cl, caught ); + } + + public void testStop() + { + String[] args = new String[] { "-c", + "foober", + "-b", + "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultiple() + { + String[] args = new String[] { "-c", + "foobar", + "-b", + "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultipleWithLong() + { + String[] args = new String[] { "--copt", + "foobar", + "--bfile", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options,args, + true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testDoubleDash() + { + String[] args = new String[] { "--copt", + "--", + "-b", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm -b is not set", ! cl.hasOption("b") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleDash() + { + String[] args = new String[] { "--copt", + "-b", "-", + "-a", + "-" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("-") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("-") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + + } +} diff --git a/installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java b/installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/HelpFormatterExamples.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: HelpFormatterExamples.java 2662 2006-02-18 14:20:33Z otmarhumbel $ + */ +package org.apache.commons.cli; + +/** + * A sample program shpwing the use of Options and the HelpFormatter class + * + * @author Slawek Zachcial + **/ +public class HelpFormatterExamples +{ + // --------------------------------------------------------------- Constants + + // ------------------------------------------------------------------ Static + + public static void main( String[] args ) + { + System.out.println("\n#\n# 'man' example\n#"); + manExample(); +/* + System.out.println("\n#\n# 'bzip2' example\n#"); + bzip2Example(); + System.out.println("\n#\n# 'ls' example\n#"); + lsExample(); +*/ + } + + static void manExample() + { + String cmdLine = + "man [-c|-f|-k|-w|-tZT device] [-adlhu7V] [-Mpath] [-Ppager] [-Slist] " + + "[-msystem] [-pstring] [-Llocale] [-eextension] [section] page ..."; + Options opts = + new Options(). + addOption("a", "all", false, "find all matching manual pages."). + addOption("d", "debug", false, "emit debugging messages."). + addOption("e", "extension", false, "limit search to extension type 'extension'."). + addOption("f", "whatis", false, "equivalent to whatis."). + addOption("k", "apropos", false, "equivalent to apropos."). + addOption("w", "location", false, "print physical location of man page(s)."). + addOption("l", "local-file", false, "interpret 'page' argument(s) as local filename(s)"). + addOption("u", "update", false, "force a cache consistency check."). + //FIXME - should generate -r,--prompt string + addOption("r", "prompt", true, "provide 'less' pager with prompt."). + addOption("c", "catman", false, "used by catman to reformat out of date cat pages."). + addOption("7", "ascii", false, "display ASCII translation or certain latin1 chars."). + addOption("t", "troff", false, "use troff format pages."). + //FIXME - should generate -T,--troff-device device + addOption("T", "troff-device", true, "use groff with selected device."). + addOption("Z", "ditroff", false, "use groff with selected device."). + addOption("D", "default", false, "reset all options to their default values."). + //FIXME - should generate -M,--manpath path + addOption("M", "manpath", true, "set search path for manual pages to 'path'."). + //FIXME - should generate -P,--pager pager + addOption("P", "pager", true, "use program 'pager' to display output."). + //FIXME - should generate -S,--sections list + addOption("S", "sections", true, "use colon separated section list."). + //FIXME - should generate -m,--systems system + addOption("m", "systems", true, "search for man pages from other unix system(s)."). + //FIXME - should generate -L,--locale locale + addOption("L", "locale", true, "defaine the locale for this particular man search."). + //FIXME - should generate -p,--preprocessor string + addOption("p", "preprocessor", true, "string indicates which preprocessor to run.\n" + + " e - [n]eqn p - pic t - tbl\n" + + " g - grap r - refer v - vgrind"). + addOption("V", "version", false, "show version."). + addOption("h", "help", false, "show this usage message."); + + HelpFormatter hf = new HelpFormatter(); + //hf.printHelp(cmdLine, opts); + hf.printHelp(60, cmdLine, null, opts, null); + } + + static void bzip2Example() + { + System.out.println( "Coming soon" ); + } + + static void lsExample() + { + System.out.println( "Coming soon" ); + } + + + // -------------------------------------------------------------- Attributes + + // ------------------------------------------------------------ Constructors + + // ------------------------------------------------------------------ Public + + // --------------------------------------------------------------- Protected + + // ------------------------------------------------------- Package protected + + // ----------------------------------------------------------------- Private + + // ----------------------------------------------------------- Inner classes + +} diff --git a/installer/test/java/org/apache/commons/cli/HelpFormatterTest.java b/installer/test/java/org/apache/commons/cli/HelpFormatterTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/HelpFormatterTest.java @@ -0,0 +1,82 @@ +package org.apache.commons.cli; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +import junit.framework.TestCase; + +public class HelpFormatterTest extends TestCase { + + private Options _options; + + protected void setUp() { + _options = new Options(); + Option aOption = new Option("a", "Aa", false, "option A"); + Option bOption = new Option("b", "Bb", false, "option B"); + OptionGroup group1 = new OptionGroup(); + group1.addOption(aOption); + group1.addOption(bOption); + _options.addOptionGroup(group1); + } + + /** + * the setUp above used to print [-a | -b] [-a] [-b] + */ + public void testOptionGroupDuplication() { + String help = unifyNewLines(getFormattedHelp()); + String expectedHelp = unifyNewLines(new String("usage: syntax [-a | -b]\n-a,--Aa option A\n-b,--Bb option B\n")); + assertEquals("expected usage to be '" + expectedHelp + "' instead of '" + help + "'", + expectedHelp, + help); + } + + /** + * Options following an option group used to be non blank separated: [-b | -a][-o] instead of + * [-b | -a] [-o] + */ + public void testOptionGroupSubsequentOptions() { + _options.addOption(new Option("o", "Option O")); + String help = getFormattedHelp(); + assertTrue(help.indexOf("][") < 0); + assertTrue(help.indexOf("[-a | -b] [-o]") >= 0); + } + + // + // private methods + // + private String getFormattedHelp() { + HelpFormatter formatter = new HelpFormatter(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter pw = new PrintWriter(baos); + formatter.printHelp(pw, 60, "syntax", null, _options, 0, 1, null, true); + pw.flush(); + String usage = baos.toString(); + return usage; + } + + /** + * replace the windows specific \r\n line endings with java like \n line endings + * + * @param in + * The string to be transformed + * @return The string with unified line endings + */ + private String unifyNewLines(String in) { + char[] inChars = in.toCharArray(); + StringBuilder b = new StringBuilder(inChars.length); + for (int i = 0; i < inChars.length; i++) { + char current = inChars[i]; + if (current == '\r') { + if (i < inChars.length) { + char next = inChars[i + 1]; + if (next == '\n') { + i++; + current = next; + } + } + } + b.append(current); + } + return b.toString(); + } +} diff --git a/installer/test/java/org/apache/commons/cli/OptionBuilderTest.java b/installer/test/java/org/apache/commons/cli/OptionBuilderTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionBuilderTest.java @@ -0,0 +1,160 @@ +package org.apache.commons.cli; + +import java.math.BigDecimal; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import junit.textui.TestRunner; + +public class OptionBuilderTest extends TestCase { + + public OptionBuilderTest( String name ) { + super( name ); + } + + public static Test suite() { + return new TestSuite( OptionBuilderTest.class ); + } + + public static void main( String args[] ) { + TestRunner.run( suite() ); + } + + public void testCompleteOption( ) { + OptionBuilder.withLongOpt( "simple option"); + OptionBuilder.hasArg( ); + OptionBuilder.isRequired( ); + OptionBuilder.hasArgs( ); + OptionBuilder.withType( new BigDecimal( "10" ) ); + OptionBuilder.withDescription( "this is a simple option" ); + Option simple = OptionBuilder.create( 's' ); + + assertEquals( "s", simple.getOpt() ); + assertEquals( "simple option", simple.getLongOpt() ); + assertEquals( "this is a simple option", simple.getDescription() ); + assertEquals( simple.getType().getClass(), BigDecimal.class ); + assertTrue( simple.hasArg() ); + assertTrue( simple.isRequired() ); + assertTrue( simple.hasArgs() ); + } + + public void testTwoCompleteOptions( ) { + OptionBuilder.withLongOpt( "simple option"); + OptionBuilder.hasArg( ); + OptionBuilder.isRequired( ); + OptionBuilder.hasArgs( ); + OptionBuilder.withType( new BigDecimal( "10" ) ); + OptionBuilder.withDescription( "this is a simple option" ); + Option simple = OptionBuilder.create( 's' ); + + assertEquals( "s", simple.getOpt() ); + assertEquals( "simple option", simple.getLongOpt() ); + assertEquals( "this is a simple option", simple.getDescription() ); + assertEquals( simple.getType().getClass(), BigDecimal.class ); + assertTrue( simple.hasArg() ); + assertTrue( simple.isRequired() ); + assertTrue( simple.hasArgs() ); + + OptionBuilder.withLongOpt( "dimple option"); + OptionBuilder.hasArg( ); + OptionBuilder.withDescription( "this is a dimple option" ); + simple = OptionBuilder.create( 'd' ); + + assertEquals( "d", simple.getOpt() ); + assertEquals( "dimple option", simple.getLongOpt() ); + assertEquals( "this is a dimple option", simple.getDescription() ); + assertNull( simple.getType() ); + assertTrue( simple.hasArg() ); + assertTrue( !simple.isRequired() ); + assertTrue( !simple.hasArgs() ); + } + + public void testBaseOptionCharOpt() { + OptionBuilder.withDescription( "option description"); + Option base = OptionBuilder.create( 'o' ); + + assertEquals( "o", base.getOpt() ); + assertEquals( "option description", base.getDescription() ); + assertTrue( !base.hasArg() ); + } + + public void testBaseOptionStringOpt() { + OptionBuilder.withDescription( "option description"); + Option base = OptionBuilder.create( "o" ); + + assertEquals( "o", base.getOpt() ); + assertEquals( "option description", base.getDescription() ); + assertTrue( !base.hasArg() ); + } + + public void testSpecialOptChars() { + + // '?' + try { + OptionBuilder.withDescription( "help options" ); + Option opt = OptionBuilder.create( '?' ); + assertEquals( "?", opt.getOpt() ); + } + catch( IllegalArgumentException arg ) { + fail( "IllegalArgumentException caught" ); + } + + // '@' + try { + OptionBuilder.withDescription( "read from stdin" ); + Option opt = OptionBuilder.create( '@' ); + assertEquals( "@", opt.getOpt() ); + } + catch( IllegalArgumentException arg ) { + fail( "IllegalArgumentException caught" ); + } + } + + public void testOptionArgNumbers() { + OptionBuilder.withDescription( "option description" ); + OptionBuilder.hasArgs( 2 ); + Option opt = OptionBuilder.create( 'o' ); + assertEquals( 2, opt.getArgs() ); + } + + public void testIllegalOptions() { + // bad single character option + try { + OptionBuilder.withDescription( "option description" ); + OptionBuilder.create( '"' ); + fail( "IllegalArgumentException not caught" ); + } + catch( IllegalArgumentException exp ) { + // success + } + + // bad character in option string + try { + OptionBuilder.create( "opt`" ); + fail( "IllegalArgumentException not caught" ); + } + catch( IllegalArgumentException exp ) { + // success + } + + // null option + try { + OptionBuilder.create( null ); + fail( "IllegalArgumentException not caught" ); + } + catch( IllegalArgumentException exp ) { + // success + } + + // valid option + try { + OptionBuilder.create( "opt" ); + // success + } + catch( IllegalArgumentException exp ) { + fail( "IllegalArgumentException caught" ); + } + } +} \ No newline at end of file diff --git a/installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java b/installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionGroupSortTest.java @@ -0,0 +1,43 @@ +package org.apache.commons.cli; + +import java.util.Collection; +import java.util.Iterator; + +import junit.framework.TestCase; + +public class OptionGroupSortTest extends TestCase { + + private OptionGroup _optionGroup; + + protected void setUp() { + _optionGroup = new OptionGroup(); + _optionGroup.addOption(new Option("f", "first", false, "first")); + _optionGroup.addOption(new Option("s", "second", false, "second")); + _optionGroup.addOption(new Option("t", "third", false, "third")); + } + + public void testSortNames() { + Collection names = _optionGroup.getNames(); + Iterator namesIterator = names.iterator(); + assertTrue(namesIterator.hasNext()); + assertEquals("-f", (String) namesIterator.next()); + assertTrue(namesIterator.hasNext()); + assertEquals("-s", (String) namesIterator.next()); + assertTrue(namesIterator.hasNext()); + assertEquals("-t", (String) namesIterator.next()); + assertFalse(namesIterator.hasNext()); + } + + public void testSortOptions() { + Collection options = _optionGroup.getOptions(); + Iterator optionIterator = options.iterator(); + assertTrue(optionIterator.hasNext()); + assertEquals("first", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("second", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("third", ((Option) optionIterator.next()).getLongOpt()); + assertFalse(optionIterator.hasNext()); + } + +} diff --git a/installer/test/java/org/apache/commons/cli/OptionGroupTest.java b/installer/test/java/org/apache/commons/cli/OptionGroupTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionGroupTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: OptionGroupTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * @author John Keyes (john at integralsource.com) + * @version $Revision: 3134 $ + */ +public class OptionGroupTest extends TestCase +{ + + private Options _options = null; + private CommandLineParser parser = new PosixParser(); + + + public static Test suite() + { + return new TestSuite ( OptionGroupTest.class ); + } + + public OptionGroupTest( String name ) + { + super( name ); + } + + public void setUp() + { + Option file = new Option( "f", "file", false, "file to process" ); + Option dir = new Option( "d", "directory", false, "directory to process" ); + OptionGroup group = new OptionGroup(); + group.addOption( file ); + group.addOption( dir ); + _options = new Options().addOptionGroup( group ); + + Option section = new Option( "s", "section", false, "section to process" ); + Option chapter = new Option( "c", "chapter", false, "chapter to process" ); + OptionGroup group2 = new OptionGroup(); + group2.addOption( section ); + group2.addOption( chapter ); + + _options.addOptionGroup( group2 ); + _options.addOption( "r", "revision", false, "revision number" ); + } + + public void tearDown() + { + } + + public void testSingleOptionFromGroup() + { + String[] args = new String[] { "-f" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleOption() + { + String[] args = new String[] { "-r" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is set", cl.hasOption("r") ); + assertTrue( "Confirm -f is NOT set", !cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testTwoValidOptions() + { + String[] args = new String[] { "-r", "-f" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is set", cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleLongOption() + { + String[] args = new String[] { "--file" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testTwoValidLongOptions() + { + String[] args = new String[] { "--revision", "--file" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is set", cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm no extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testNoOptionsExtraArgs() + { + String[] args = new String[] { "arg1", "arg2" }; + + try + { + CommandLine cl = parser.parse( _options, args); + + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is NOT set", !cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is NOT set", !cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm TWO extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testTwoOptionsFromGroup() + { + String[] args = new String[] { "-f", "-d" }; + + CommandLine cl = null; + try + { + cl = parser.parse( _options, args); + fail( "two arguments from group not allowed" ); + } + catch (ParseException e) + { + if( !( e instanceof AlreadySelectedException ) ) + { + fail( "incorrect exception caught:" + e.getMessage() + " in " + cl ); + } + } + } + + public void testTwoLongOptionsFromGroup() + { + String[] args = new String[] { "--file", "--directory" }; + + CommandLine cl = null; + try + { + cl = parser.parse( _options, args); + fail( "two arguments from group not allowed" ); + } + catch (ParseException e) + { + if( !( e instanceof AlreadySelectedException ) ) + { + fail( "incorrect exception caught:" + e.getMessage() + " in " + cl ); + } + } + } + + public void testTwoOptionsFromDifferentGroup() + { + String[] args = new String[] { "-f", "-s" }; + + try + { + CommandLine cl = parser.parse( _options, args); + assertTrue( "Confirm -r is NOT set", !cl.hasOption("r") ); + assertTrue( "Confirm -f is set", cl.hasOption("f") ); + assertTrue( "Confirm -d is NOT set", !cl.hasOption("d") ); + assertTrue( "Confirm -s is set", cl.hasOption("s") ); + assertTrue( "Confirm -c is NOT set", !cl.hasOption("c") ); + assertTrue( "Confirm NO extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + +} diff --git a/installer/test/java/org/apache/commons/cli/OptionsTest.java b/installer/test/java/org/apache/commons/cli/OptionsTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/OptionsTest.java @@ -0,0 +1,28 @@ +package org.apache.commons.cli; + +import java.util.Collection; +import java.util.Iterator; + +import junit.framework.TestCase; + +public class OptionsTest extends TestCase { + + private Options _options; + + public void testSortAsAdded() { + _options = new Options(); + _options.setSortAsAdded(true); + _options.addOption("f", "first", false, "first"); + _options.addOption("s", "second", false, "second"); + _options.addOption("t", "third", false, "third"); + Collection optionCollection = _options.getOptions(); + Iterator optionIterator = optionCollection.iterator(); + assertTrue(optionIterator.hasNext()); + assertEquals("first", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("second", ((Option) optionIterator.next()).getLongOpt()); + assertTrue(optionIterator.hasNext()); + assertEquals("third", ((Option) optionIterator.next()).getLongOpt()); + assertFalse(optionIterator.hasNext()); + } +} diff --git a/installer/test/java/org/apache/commons/cli/ParseRequiredTest.java b/installer/test/java/org/apache/commons/cli/ParseRequiredTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ParseRequiredTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ParseRequiredTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * @author John Keyes (john at integralsource.com) + * @version $Revision: 3134 $ + */ +public class ParseRequiredTest extends TestCase +{ + + private Options _options = null; + private CommandLineParser parser = new PosixParser(); + + public static Test suite() { + return new TestSuite(ParseRequiredTest.class); + } + + public ParseRequiredTest(String name) + { + super(name); + } + + public void setUp() + { + OptionBuilder.withLongOpt( "bfile" ); + OptionBuilder.hasArg(); + OptionBuilder.isRequired(); + OptionBuilder.withDescription( "set the value of [b]" ); + Option opt2 = OptionBuilder.create( 'b' ); + _options = new Options() + .addOption("a", + "enable-a", + false, + "turn [a] on or off") + .addOption( opt2 ); + } + + public void tearDown() + { + + } + + public void testWithRequiredOption() + { + String[] args = new String[] { "-b", "file" }; + + try + { + CommandLine cl = parser.parse(_options,args); + + assertTrue( "Confirm -a is NOT set", !cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("file") ); + assertTrue( "Confirm NO of extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testOptionAndRequiredOption() + { + String[] args = new String[] { "-a", "-b", "file" }; + + try + { + CommandLine cl = parser.parse(_options,args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("file") ); + assertTrue( "Confirm NO of extra args", cl.getArgList().size() == 0); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMissingRequiredOption() + { + String[] args = new String[] { "-a" }; + + CommandLine cl = null; + try + { + cl = parser.parse(_options,args); + fail( "exception should have been thrown" ); + } + catch (ParseException e) + { + if( !( e instanceof MissingOptionException ) ) + { + fail( "expected to catch MissingOptionException in " + cl ); + } + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/ParseTest.java b/installer/test/java/org/apache/commons/cli/ParseTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ParseTest.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ParseTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class ParseTest extends TestCase +{ + + private Options _options = null; + private CommandLineParser _parser = null; + + public static Test suite() { + return new TestSuite(ParseTest.class); + } + + public ParseTest(String name) + { + super(name); + } + + public void setUp() + { + _options = new Options() + .addOption("a", + "enable-a", + false, + "turn [a] on or off") + .addOption("b", + "bfile", + true, + "set the value of [b]") + .addOption("c", + "copt", + false, + "turn [c] on or off"); + + _parser = new PosixParser(); + } + + public void tearDown() + { + + } + + public void testSimpleShort() + { + String[] args = new String[] { "-a", + "-b", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSimpleLong() + { + String[] args = new String[] { "--enable-a", + "--bfile", "toast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testComplexShort() + { + String[] args = new String[] { "-acbtoast", + "foo", "bar" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testExtraOption() + { + String[] args = new String[] { "-adbtoast", + "foo", "bar" }; + + boolean caught = false; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm size of extra args", cl.getArgList().size() == 3); + } + catch (UnrecognizedOptionException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + assertTrue( "Confirm UnrecognizedOptionException caught", caught ); + } + + public void testMissingArg() + { + + String[] args = new String[] { "-acb" }; + + boolean caught = false; + + CommandLine cl = null; + try + { + cl = _parser.parse(_options, args); + } + catch (MissingArgumentException e) + { + caught = true; + } + catch (ParseException e) + { + fail( e.toString() ); + } + + assertTrue( "Confirm MissingArgumentException caught " + cl, caught ); + } + + public void testStop() + { + String[] args = new String[] { "-c", + "foober", + "-btoast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultiple() + { + String[] args = new String[] { "-c", + "foobar", + "-btoast" }; + + try + { + CommandLine cl = _parser.parse(_options, args, true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testMultipleWithLong() + { + String[] args = new String[] { "--copt", + "foobar", + "--bfile", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options,args, + true); + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm 3 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 3); + + cl = _parser.parse(_options, cl.getArgs() ); + + assertTrue( "Confirm -c is not set", ! cl.hasOption("c") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("toast") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("foobar") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testDoubleDash() + { + String[] args = new String[] { "--copt", + "--", + "-b", "toast" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -c is set", cl.hasOption("c") ); + assertTrue( "Confirm -b is not set", ! cl.hasOption("b") ); + assertTrue( "Confirm 2 extra args: " + cl.getArgList().size(), cl.getArgList().size() == 2); + + } + catch (ParseException e) + { + fail( e.toString() ); + } + } + + public void testSingleDash() + { + String[] args = new String[] { "--copt", + "-b", "-", + "-a", + "-" }; + + try + { + CommandLine cl = _parser.parse(_options, args); + + assertTrue( "Confirm -a is set", cl.hasOption("a") ); + assertTrue( "Confirm -b is set", cl.hasOption("b") ); + assertTrue( "Confirm arg of -b", cl.getOptionValue("b").equals("-") ); + assertTrue( "Confirm 1 extra arg: " + cl.getArgList().size(), cl.getArgList().size() == 1); + assertTrue( "Confirm value of extra arg: " + cl.getArgList().get(0), cl.getArgList().get(0).equals("-") ); + } + catch (ParseException e) + { + fail( e.toString() ); + } + + } +} diff --git a/installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java b/installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/PatternOptionBuilderTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: PatternOptionBuilderTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ +package org.apache.commons.cli; + +import java.math.BigDecimal; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test case for the PatternOptionBuilder class + * + * @author Henri Yandell + **/ +public class PatternOptionBuilderTest +extends TestCase +{ + public static void main( String[] args ) + { + String[] testName = { PatternOptionBuilderTest.class.getName() }; + junit.textui.TestRunner.main(testName); + } + + public static TestSuite suite() + { + return new TestSuite(PatternOptionBuilderTest.class); + } + + public PatternOptionBuilderTest( String s ) + { + super( s ); + } + + public void testSimplePattern() + { + try { + Options options = PatternOptionBuilder.parsePattern("a:b at cde>f+n%t/"); + String[] args = new String[] { "-c", "-a", "foo", "-b", "java.util.Vector", "-e", "build.xml", "-f", "java.util.Calendar", "-n", "4.5", "-t", "http://jakarta.apache.org/" }; + + CommandLineParser parser = new PosixParser(); + CommandLine line = parser.parse(options,args); + + // tests the char methods of CommandLine that delegate to + // the String methods + assertEquals("flag a", "foo", line.getOptionValue("a")); + assertEquals("flag a", "foo", line.getOptionValue('a')); + assertEquals("string flag a", "foo", line.getOptionObject("a")); + assertEquals("string flag a", "foo", line.getOptionObject('a')); + assertEquals("object flag b", new java.util.Vector(), line.getOptionObject("b")); + assertEquals("object flag b", new java.util.Vector(), line.getOptionObject('b')); + assertEquals("boolean true flag c", true, line.hasOption("c")); + assertEquals("boolean true flag c", true, line.hasOption('c')); + assertEquals("boolean false flag d", false, line.hasOption("d")); + assertEquals("boolean false flag d", false, line.hasOption('d')); + assertEquals("file flag e", new java.io.File("build.xml"), line.getOptionObject("e")); + assertEquals("file flag e", new java.io.File("build.xml"), line.getOptionObject('e')); + assertEquals("class flag f", java.util.Calendar.class, line.getOptionObject("f")); + assertEquals("class flag f", java.util.Calendar.class, line.getOptionObject('f')); + assertEquals("number flag n", new BigDecimal("4.5"), line.getOptionObject("n")); + assertEquals("number flag n", new BigDecimal("4.5"), line.getOptionObject('n')); + assertEquals("url flag t", new java.net.URL("http://jakarta.apache.org/"), line.getOptionObject("t")); + assertEquals("url flag t", new java.net.URL("http://jakarta.apache.org/"), line.getOptionObject('t')); + /// DATES NOT SUPPORTED YET. + // assertEquals("number flag t", new java.util.Date(1023400137276L), line.getOptionObject('z')); + // input is: "Thu Jun 06 17:48:57 EDT 2002" + } + catch( ParseException exp ) { + fail( exp.getMessage() ); + } + catch( java.net.MalformedURLException exp ) { + fail( exp.getMessage() ); + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/PosixParserTest.java b/installer/test/java/org/apache/commons/cli/PosixParserTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/PosixParserTest.java @@ -0,0 +1,138 @@ +package org.apache.commons.cli; + +import junit.framework.TestCase; + +public class PosixParserTest extends TestCase { + + private static final String TEST_SHORT = "t"; + private static final String TEST_LONG = "test"; + private static final String TEST_DESC = "test option"; + + private static final String TEST_SHORT_OPTION = "-t"; + private static final String TEST_LONG_OPTION = "--test"; + + private static final String ARGUMENT = "argument"; + + private Options _options; + private Parser _parser; + + protected void setUp() { + _parser = new PosixParser(); + + _options = new Options(); + Option testOption = new Option(TEST_SHORT, TEST_LONG, false, TEST_DESC); + _options.addOption(testOption); + } + + /** + * test that an unknown single option and a double hyphen option (with or without argument) are treated the same + */ + public void testFlattenStop() { + boolean stopAtNonOption = true; // means unallowed tokens should not be added + String[] args; + String[] expectedFlattened; + + // unknown single dash option + args = new String[] { "-u" }; + expectedFlattened = new String[0]; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "-u", TEST_SHORT_OPTION }; + expectedFlattened = new String[] { TEST_SHORT_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option + args = new String[] { "--unknown" }; + expectedFlattened = new String[0]; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", TEST_LONG_OPTION }; + expectedFlattened = new String[] { TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after = + args = new String[] { "--unknown=" + ARGUMENT }; + expectedFlattened = new String[0]; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown="+ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after ' ' + args = new String[] { "--unknown", ARGUMENT }; + expectedFlattened = new String[] { ARGUMENT }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { ARGUMENT, TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + } + + /** + * test that an unknown single option and a double hyphen option (with or without argument) are treated the same + */ + public void testFlattenNoStop() { + boolean stopAtNonOption = false; // means every token should be added + String[] args; + String[] expectedFlattened; + + // unknown single dash option + args = new String[] { "-u" }; + expectedFlattened = new String[] { "-u" }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "-u", TEST_SHORT_OPTION }; + expectedFlattened = new String[] { "-u", TEST_SHORT_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option + args = new String[] { "--unknown" }; + expectedFlattened = new String[] { "--unknown" }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", TEST_LONG_OPTION }; + expectedFlattened = new String[] { "--unknown", TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after = + args = new String[] { "--unknown=" + ARGUMENT }; + expectedFlattened = new String[] { "--unknown", ARGUMENT }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown="+ ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + + // unknown double dash option with argument after ' ' + args = new String[] { "--unknown", ARGUMENT }; + expectedFlattened = new String[] { "--unknown", ARGUMENT }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + args = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + expectedFlattened = new String[] { "--unknown", ARGUMENT, TEST_LONG_OPTION }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + } + + /** + * test that a misspelled long option (-test instead of --test) is not interpreted as -t est + */ + public void testMisspelledLongOption() { + boolean stopAtNonOption = false; // means every token should be added + String[] args; + String[] expectedFlattened; + + // unknown single dash long option + String singleDashLongOption = "-" + TEST_LONG; + args = new String[] { singleDashLongOption }; + expectedFlattened = new String[] { singleDashLongOption }; + assertEquals(expectedFlattened, _parser.flatten(_options, args, stopAtNonOption)); + } + + // + // private stuff + // + + /** + * Assert that the content of the specified object arrays is equal + */ + private void assertEquals(Object[] correct, Object[] tested) { + assertEquals("different array lengths:", correct.length, tested.length); + for (int i = 0; i < correct.length; i++) { + assertEquals("position " + i + " of array differs", correct[i], tested[i]); + } + } + +} diff --git a/installer/test/java/org/apache/commons/cli/TestHelpFormatter.java b/installer/test/java/org/apache/commons/cli/TestHelpFormatter.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/TestHelpFormatter.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: TestHelpFormatter.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ +package org.apache.commons.cli; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Test case for the HelpFormatter class + * + * @author Slawek Zachcial + * @author John Keyes ( john at integralsource.com ) + **/ +public class TestHelpFormatter extends TestCase +{ + public static void main( String[] args ) + { + String[] testName = { TestHelpFormatter.class.getName() }; + junit.textui.TestRunner.main(testName); + } + + public static TestSuite suite() + { + return new TestSuite(TestHelpFormatter.class); + } + + public TestHelpFormatter( String s ) + { + super( s ); + } + + public void testFindWrapPos() + throws Exception + { + HelpFormatter hf = new HelpFormatter(); + + String text = "This is a test."; + //text width should be max 8; the wrap postition is 7 + assertEquals("wrap position", 7, hf.findWrapPos(text, 8, 0)); + //starting from 8 must give -1 - the wrap pos is after end + assertEquals("wrap position 2", -1, hf.findWrapPos(text, 8, 8)); + //if there is no a good position before width to make a wrapping look for the next one + text = "aaaa aa"; + assertEquals("wrap position 3", 4, hf.findWrapPos(text, 3, 0)); + } + + public void testPrintWrapped() + throws Exception + { + StringBuffer sb = new StringBuffer(); + HelpFormatter hf = new HelpFormatter(); + + String text = "This is a test."; + String expected; + + expected = "This is a" + hf.defaultNewLine + "test."; + hf.renderWrappedText(sb, 12, 0, text); + assertEquals("single line text", expected, sb.toString()); + + sb.setLength(0); + expected = "This is a" + hf.defaultNewLine + " test."; + hf.renderWrappedText(sb, 12, 4, text); + assertEquals("single line padded text", expected, sb.toString()); + + text = + "aaaa aaaa aaaa" + hf.defaultNewLine + + "aaaaaa" + hf.defaultNewLine + + "aaaaa"; + + expected = text; + sb.setLength(0); + hf.renderWrappedText(sb, 16, 0, text); + assertEquals("multi line text", expected, sb.toString()); + + expected = + "aaaa aaaa aaaa" + hf.defaultNewLine + + " aaaaaa" + hf.defaultNewLine + + " aaaaa"; + sb.setLength(0); + hf.renderWrappedText(sb, 16, 4, text); + assertEquals("multi-line padded text", expected, sb.toString()); + } + + public void testPrintOptions() + throws Exception + { + StringBuffer sb = new StringBuffer(); + HelpFormatter hf = new HelpFormatter(); + final int leftPad = 1; + final int descPad = 3; + final String lpad = hf.createPadding(leftPad); + final String dpad = hf.createPadding(descPad); + Options options = null; + String expected = null; + + options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa"); + expected = lpad + "-a" + dpad + "aaaa aaaa aaaa aaaa aaaa"; + hf.renderOptions(sb, 60, options, leftPad, descPad); + assertEquals("simple non-wrapped option", expected, sb.toString()); + + int nextLineTabStop = leftPad+descPad+"-a".length(); + expected = + lpad + "-a" + dpad + "aaaa aaaa aaaa" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "aaaa aaaa"; + sb.setLength(0); + hf.renderOptions(sb, nextLineTabStop+17, options, leftPad, descPad); + assertEquals("simple wrapped option", expected, sb.toString()); + + + options = new Options().addOption("a", "aaa", false, "dddd dddd dddd dddd"); + expected = lpad + "-a,--aaa" + dpad + "dddd dddd dddd dddd"; + sb.setLength(0); + hf.renderOptions(sb, 60, options, leftPad, descPad); + assertEquals("long non-wrapped option", expected, sb.toString()); + + nextLineTabStop = leftPad+descPad+"-a,--aaa".length(); + expected = + lpad + "-a,--aaa" + dpad + "dddd dddd" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "dddd dddd"; + sb.setLength(0); + hf.renderOptions(sb, 25, options, leftPad, descPad); + assertEquals("long wrapped option", expected, sb.toString()); + + options = new Options(). + addOption("a", "aaa", false, "dddd dddd dddd dddd"). + addOption("b", false, "feeee eeee eeee eeee"); + expected = + lpad + "-a,--aaa" + dpad + "dddd dddd" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "dddd dddd" + hf.defaultNewLine + + lpad + "-b " + dpad + "feeee eeee" + hf.defaultNewLine + + hf.createPadding(nextLineTabStop) + "eeee eeee"; + sb.setLength(0); + hf.renderOptions(sb, 25, options, leftPad, descPad); + assertEquals("multiple wrapped options", expected, sb.toString()); + } + + public void testAutomaticUsage() + throws Exception + { + HelpFormatter hf = new HelpFormatter(); + Options options = null; + String expected = "usage: app [-a]"; + ByteArrayOutputStream out = new ByteArrayOutputStream( ); + PrintWriter pw = new PrintWriter( out ); + + options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa"); + hf.printUsage( pw, 60, "app", options ); + pw.flush(); + assertEquals("simple auto usage", expected, out.toString().trim()); + out.reset(); + + expected = "usage: app [-b] [-a]"; + options = new Options().addOption("a", false, "aaaa aaaa aaaa aaaa aaaa") + .addOption("b", false, "bbb" ); + hf.printUsage( pw, 60, "app", options ); + pw.flush(); + assertEquals("simple auto usage", expected, out.toString().trim()); + out.reset(); + } +} diff --git a/installer/test/java/org/apache/commons/cli/ValueTest.java b/installer/test/java/org/apache/commons/cli/ValueTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ValueTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ValueTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class ValueTest extends TestCase +{ + + public static Test suite() { + return new TestSuite(ValueTest.class); + } + + private CommandLine _cl = null; + private Options opts = new Options(); + + public ValueTest(String name) + { + super(name); + } + + public void setUp() + { + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("b", + true, + "set -b"); + + opts.addOption("c", + "c", + false, + "toggle -c"); + + opts.addOption("d", + "d", + true, + "set -d"); + + OptionBuilder.hasOptionalArg(); + opts.addOption( OptionBuilder.create( 'e') ); + + OptionBuilder.hasOptionalArg(); + OptionBuilder.withLongOpt( "fish" ); + opts.addOption( OptionBuilder.create( ) ); + + OptionBuilder.hasOptionalArgs(); + OptionBuilder.withLongOpt( "gravy" ); + opts.addOption( OptionBuilder.create( ) ); + + OptionBuilder.hasOptionalArgs( 2 ); + OptionBuilder.withLongOpt( "hide" ); + opts.addOption( OptionBuilder.create( ) ); + + OptionBuilder.hasOptionalArgs( 2 ); + opts.addOption( OptionBuilder.create( 'i' ) ); + + OptionBuilder.hasOptionalArgs( ); + opts.addOption( OptionBuilder.create( 'j' ) ); + + String[] args = new String[] { "-a", + "-b", "foo", + "--c", + "--d", "bar" + }; + + try + { + CommandLineParser parser = new PosixParser(); + _cl = parser.parse(opts,args); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void tearDown() + { + + } + + public void testShortNoArg() + { + assertTrue( _cl.hasOption("a") ); + assertNull( _cl.getOptionValue("a") ); + } + + public void testShortWithArg() + { + assertTrue( _cl.hasOption("b") ); + assertNotNull( _cl.getOptionValue("b") ); + assertEquals( _cl.getOptionValue("b"), "foo"); + } + + public void testLongNoArg() + { + assertTrue( _cl.hasOption("c") ); + assertNull( _cl.getOptionValue("c") ); + } + + public void testLongWithArg() + { + assertTrue( _cl.hasOption("d") ); + assertNotNull( _cl.getOptionValue("d") ); + assertEquals( _cl.getOptionValue("d"), "bar"); + } + + public void testShortOptionalArgNoValue() + { + String[] args = new String[] { "-e" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("e") ); + assertNull( cmd.getOptionValue("e") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testShortOptionalArgValue() + { + String[] args = new String[] { "-e", "everything" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("e") ); + assertEquals( "everything", cmd.getOptionValue("e") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalNoValue() + { + String[] args = new String[] { "--fish" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("fish") ); + assertNull( cmd.getOptionValue("fish") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalArgValue() + { + String[] args = new String[] { "--fish", "face" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("fish") ); + assertEquals( "face", cmd.getOptionValue("fish") ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testShortOptionalArgValues() + { + String[] args = new String[] { "-j", "ink", "idea" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("j") ); + assertEquals( "ink", cmd.getOptionValue("j") ); + assertEquals( "ink", cmd.getOptionValues("j")[0] ); + assertEquals( "idea", cmd.getOptionValues("j")[1] ); + assertEquals( cmd.getArgs().length, 0 ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalArgValues() + { + String[] args = new String[] { "--gravy", "gold", "garden" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("gravy") ); + assertEquals( "gold", cmd.getOptionValue("gravy") ); + assertEquals( "gold", cmd.getOptionValues("gravy")[0] ); + assertEquals( "garden", cmd.getOptionValues("gravy")[1] ); + assertEquals( cmd.getArgs().length, 0 ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testShortOptionalNArgValues() + { + String[] args = new String[] { "-i", "ink", "idea", "isotope", "ice" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("i") ); + assertEquals( "ink", cmd.getOptionValue("i") ); + assertEquals( "ink", cmd.getOptionValues("i")[0] ); + assertEquals( "idea", cmd.getOptionValues("i")[1] ); + assertEquals( cmd.getArgs().length, 2 ); + assertEquals( "isotope", cmd.getArgs()[0] ); + assertEquals( "ice", cmd.getArgs()[1] ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void testLongOptionalNArgValues() + { + String[] args = new String[] { "--hide", "house", "hair", "head" + }; + try + { + CommandLineParser parser = new PosixParser(); + CommandLine cmd = parser.parse(opts,args); + assertTrue( cmd.hasOption("hide") ); + assertEquals( "house", cmd.getOptionValue("hide") ); + assertEquals( "house", cmd.getOptionValues("hide")[0] ); + assertEquals( "hair", cmd.getOptionValues("hide")[1] ); + assertEquals( cmd.getArgs().length, 1 ); + assertEquals( "head", cmd.getArgs()[0] ); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } +} diff --git a/installer/test/java/org/apache/commons/cli/ValuesTest.java b/installer/test/java/org/apache/commons/cli/ValuesTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/apache/commons/cli/ValuesTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + * + * $Id: ValuesTest.java 3134 2007-03-02 07:20:08Z otmarhumbel $ + */ + +package org.apache.commons.cli; + +import java.util.Arrays; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +public class ValuesTest extends TestCase +{ + /** CommandLine instance */ + private CommandLine _cmdline = null; + private Option _option = null; + + public static Test suite() { + return new TestSuite( ValuesTest.class ); + } + + public ValuesTest( String name ) + { + super( name ); + } + + public void setUp() + { + Options opts = new Options(); + + opts.addOption("a", + false, + "toggle -a"); + + opts.addOption("b", + true, + "set -b"); + + opts.addOption("c", + "c", + false, + "toggle -c"); + + opts.addOption("d", + "d", + true, + "set -d"); + + OptionBuilder.withLongOpt( "e" ); + OptionBuilder.hasArgs(); + OptionBuilder.withDescription( "set -e "); + opts.addOption( OptionBuilder.create( 'e' ) ); + + opts.addOption("f", + "f", + false, + "jk"); + + OptionBuilder.withLongOpt( "g" ); + OptionBuilder.hasArgs( 2 ); + OptionBuilder.withDescription( "set -g"); + opts.addOption( OptionBuilder.create( 'g' ) ); + + OptionBuilder.withLongOpt( "h" ); + OptionBuilder.hasArgs( 2 ); + OptionBuilder.withDescription( "set -h"); + opts.addOption( OptionBuilder.create( 'h' ) ); + + OptionBuilder.withLongOpt( "i" ); + OptionBuilder.withDescription( "set -i"); + opts.addOption( OptionBuilder.create( 'i' ) ); + + OptionBuilder.withLongOpt( "j" ); + OptionBuilder.hasArgs( ); + OptionBuilder.withDescription( "set -j"); + OptionBuilder.withValueSeparator( '=' ); + opts.addOption( OptionBuilder.create( 'j' ) ); + + OptionBuilder.withLongOpt( "k" ); + OptionBuilder.hasArgs( ); + OptionBuilder.withDescription( "set -k"); + OptionBuilder.withValueSeparator( '=' ); + opts.addOption( OptionBuilder.create( 'k' ) ); + + OptionBuilder.withLongOpt( "m" ); + OptionBuilder.hasArgs( ); + OptionBuilder.withDescription( "set -m"); + OptionBuilder.withValueSeparator( ); + _option = OptionBuilder.create( 'm' ); + + opts.addOption( _option ); + + String[] args = new String[] { "-a", + "-b", "foo", + "--c", + "--d", "bar", + "-e", "one", "two", + "-f", + "arg1", "arg2", + "-g", "val1", "val2" , "arg3", + "-h", "val1", "-i", + "-h", "val2", + "-jkey=value", + "-j", "key=value", + "-kkey1=value1", + "-kkey2=value2", + "-mkey=value"}; + + CommandLineParser parser = new PosixParser(); + + try + { + _cmdline = parser.parse(opts,args); + } + catch (ParseException e) + { + fail("Cannot setUp() CommandLine: " + e.toString()); + } + } + + public void tearDown() + { + + } + + public void testShortArgs() + { + assertTrue( _cmdline.hasOption("a") ); + assertTrue( _cmdline.hasOption("c") ); + + assertNull( _cmdline.getOptionValues("a") ); + assertNull( _cmdline.getOptionValues("c") ); + } + + public void testShortArgsWithValue() + { + assertTrue( _cmdline.hasOption("b") ); + assertTrue( _cmdline.getOptionValue("b").equals("foo")); + assertTrue( _cmdline.getOptionValues("b").length == 1); + + assertTrue( _cmdline.hasOption("d") ); + assertTrue( _cmdline.getOptionValue("d").equals("bar")); + assertTrue( _cmdline.getOptionValues("d").length == 1); + } + + public void testMultipleArgValues() + { + _cmdline.getOptionValues("e"); + String[] values = new String[] { "one", "two" }; + assertTrue( _cmdline.hasOption("e") ); + assertTrue( _cmdline.getOptionValues("e").length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues("e") ) ); + } + + public void testTwoArgValues() + { + _cmdline.getOptionValues("g"); + String[] values = new String[] { "val1", "val2" }; + assertTrue( _cmdline.hasOption("g") ); + assertTrue( _cmdline.getOptionValues("g").length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues("g") ) ); + } + + public void testComplexValues() + { + _cmdline.getOptionValues("h"); + String[] values = new String[] { "val1", "val2" }; + assertTrue( _cmdline.hasOption("i") ); + assertTrue( _cmdline.hasOption("h") ); + assertTrue( _cmdline.getOptionValues("h").length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues("h") ) ); + } + + public void testExtraArgs() + { + String[] args = new String[] { "arg1", "arg2", "arg3" }; + assertTrue( _cmdline.getArgs().length == 3 ); + assertTrue( Arrays.equals( args, _cmdline.getArgs() ) ); + } + + public void testCharSeparator() + { + // tests the char methods of CommandLine that delegate to + // the String methods + String[] values = new String[] { "key", "value", "key", "value" }; + assertTrue( _cmdline.hasOption( "j" ) ); + assertTrue( _cmdline.hasOption( 'j' ) ); + assertTrue( _cmdline.getOptionValues( "j" ).length == 4); + assertTrue( _cmdline.getOptionValues( 'j' ).length == 4); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( "j" ) ) ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( 'j' ) ) ); + + values = new String[] { "key1", "value1", "key2", "value2" }; + assertTrue( _cmdline.hasOption( "k" ) ); + assertTrue( _cmdline.hasOption( 'k' ) ); + assertTrue( _cmdline.getOptionValues( "k" ).length == 4 ); + assertTrue( _cmdline.getOptionValues( 'k' ).length == 4 ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( "k" ) ) ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( 'k' ) ) ); + + values = new String[] { "key", "value" }; + assertTrue( _cmdline.hasOption( "m" ) ); + assertTrue( _cmdline.hasOption( 'm' ) ); + assertTrue( _cmdline.getOptionValues( "m" ).length == 2); + assertTrue( _cmdline.getOptionValues( 'm' ).length == 2); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( "m" ) ) ); + assertTrue( Arrays.equals( values, _cmdline.getOptionValues( 'm' ) ) ); + } + + /** + * jkeyes - commented out this test as the new architecture + * breaks this type of functionality. I have left the test + * here in case I get a brainwave on how to resolve this. + */ + /* + public void testGetValue() + { + // the 'm' option + assertTrue( _option.getValues().length == 2 ); + assertEquals( _option.getValue(), "key" ); + assertEquals( _option.getValue( 0 ), "key" ); + assertEquals( _option.getValue( 1 ), "value" ); + + try { + assertEquals( _option.getValue( 2 ), "key" ); + fail( "IndexOutOfBounds not caught" ); + } + catch( IndexOutOfBoundsException exp ) { + + } + + try { + assertEquals( _option.getValue( -1 ), "key" ); + fail( "IndexOutOfBounds not caught" ); + } + catch( IndexOutOfBoundsException exp ) { + + } + } + */ +} diff --git a/installer/test/java/org/python/util/install/ChildProcessExample.java b/installer/test/java/org/python/util/install/ChildProcessExample.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/ChildProcessExample.java @@ -0,0 +1,32 @@ + +package org.python.util.install; + +/** + * An example child process that generates some output. + */ +public class ChildProcessExample { + + public ChildProcessExample() { + System.out.println("[ChildProcessExample] is now here."); + } + + public static void main(String args[]) { + int i = 0; + new ChildProcessExample(); + for (i = 0; i < 10; i++) { + System.out.println("[ChildProcessExample] printing to stdout " + i); + // occasionally print to stderr, too + if (i % 3 == 0) { + System.err.println("[ChildProcessExample] printing to stderr " + i); + } + try { + Thread.sleep(500); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + System.out.println("[ChildProcessExample] Exiting"); + System.exit(0); + } + +} \ No newline at end of file diff --git a/installer/test/java/org/python/util/install/ChildProcessTest.java b/installer/test/java/org/python/util/install/ChildProcessTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/ChildProcessTest.java @@ -0,0 +1,80 @@ +package org.python.util.install; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +import junit.framework.TestCase; + +public class ChildProcessTest extends TestCase { + + private final static String CLASS_NAME = "org.python.util.install.ChildProcessExample"; + + /** + * test a default child process + */ + public void testDefault() { + ChildProcess childProcess = new ChildProcess(); + String command[] = buildJavaCommand(CLASS_NAME); + childProcess.setCommand(command); + childProcess.setDebug(true); + int exitValue = childProcess.run(); + assertEquals("Expected child process to end normally instead of " + exitValue, 0, exitValue); + } + + /** + * test the child process with a timeout + */ + public void testTimeout() { + ChildProcess childProcess = new ChildProcess(); + String command[] = buildJavaCommand(CLASS_NAME); + childProcess.setCommand(command); + childProcess.setDebug(true); + childProcess.setTimeout(2000); // timeout to 2 seconds + int exitValue = childProcess.run(); + assertEquals("Expected child process to be destroyed instead of " + exitValue, + ChildProcess.DESTROYED_AFTER_TIMEOUT, + exitValue); + } + + /** + * test silent mode + */ + public void testSilent() throws IOException { + ChildProcess childProcess = new ChildProcess(); + String command[] = new String[] {"lwiklsl", "-siwK"}; + childProcess.setCommand(command); + childProcess.setDebug(false); + childProcess.setSilent(true); + ByteArrayOutputStream redirectedErr = new ByteArrayOutputStream(); + ByteArrayOutputStream redirectedOut = new ByteArrayOutputStream(); + int exitValue = 0; + PrintStream oldErr = System.err; + PrintStream oldOut = System.out; + try { + System.setErr(new PrintStream(redirectedErr)); + System.setOut(new PrintStream(redirectedOut)); + exitValue = childProcess.run(); + } finally { + System.setErr(oldErr); + System.setOut(oldOut); + } + assertTrue(0 != exitValue); + redirectedErr.flush(); + redirectedOut.flush(); + assertEquals(0, redirectedErr.size()); + assertEquals(0, redirectedOut.size()); + } + + // + // private methods + // + private String[] buildJavaCommand(String classAndArguments) { + String quote = ""; + if (System.getProperty("os.name", "unknown").toLowerCase().indexOf("windows") >= 0) { + quote = "\""; + } + String classpath = System.getProperty("java.class.path"); + return new String[] {"java", "-classpath", quote.concat(classpath).concat(quote), classAndArguments}; + } +} \ No newline at end of file diff --git a/installer/test/java/org/python/util/install/ChmodTest_Standalone.java b/installer/test/java/org/python/util/install/ChmodTest_Standalone.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/ChmodTest_Standalone.java @@ -0,0 +1,57 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; + +/** + * Helper class to test of 'chmod' on different platforms + */ +public class ChmodTest_Standalone { + + private static String _mode = "755"; // default mode + + public static void main(String[] args) { + // get mode from first argument, if present + if (args.length > 0) { + _mode = args[0]; + } + + // create an empty test file in the current directory + String curdir = System.getProperty("user.dir"); + File testFile = new File(curdir, "chmod.test"); + String path = testFile.getAbsolutePath(); + if (!testFile.exists()) { + try { + if (!testFile.createNewFile()) { + System.err.println(getPrefix() + "unable to create file " + path); + System.exit(1); + } + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + } + + // apply the chmod command on the test file + if (!testFile.exists()) { + System.err.println(getPrefix() + "unable to create file " + path); + System.exit(1); + } else { + String command[] = new String[] {"chmod", _mode, path}; + ChildProcess childProcess = new ChildProcess(command, 3000); + childProcess.setDebug(true); + int exitValue = childProcess.run(); + if (exitValue != 0) { + System.err.println(getPrefix() + "error during chmod"); + } else { + System.out.println(getPrefix() + "chmod command executed on " + path); + } + System.exit(exitValue); + } + } + + private static String getPrefix() { + return "[ChmodTest_Standalone] "; + } + +} diff --git a/installer/test/java/org/python/util/install/FileHelperTest.java b/installer/test/java/org/python/util/install/FileHelperTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/FileHelperTest.java @@ -0,0 +1,271 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; + +import junit.framework.TestCase; +import junit.runner.TestSuiteLoader; + +import org.python.util.install.driver.Autotest; + +public class FileHelperTest extends TestCase { + + private static final String JYTHON_TEST_TEMPLATE = "jython_test.template"; + + private static final String LOGO_GIF = "logo.gif"; + + private static final String JYTHON_SMALL_C_PNG = "jython_small_c.png"; + + public void testCreateTempDirectory_WasFile() throws IOException { + File file = File.createTempFile("some_prefix", ""); + assertTrue(file.exists()); + assertTrue(file.isFile()); + assertTrue(FileHelper.createTempDirectory(file)); + assertTrue(file.exists()); + assertTrue(file.isDirectory()); + } + + public void testCreateTempDirectory_AlreadyPresent() throws IOException { + File dir = new File(System.getProperty("user.dir")); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + assertTrue(FileHelper.createTempDirectory(dir)); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + } + + public void testCreateTempDirectory() throws IOException { + File dir = new File(System.getProperty("user.dir")); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + File tmpDir = new File(dir, "tmp"); + assertFalse(tmpDir.exists()); + try { + assertTrue(FileHelper.createTempDirectory(tmpDir)); + assertTrue(tmpDir.exists()); + assertTrue(dir.isDirectory()); + } finally { + if (tmpDir.exists()) { + assertTrue(tmpDir.delete()); + } + } + } + + public void testCreateTempDirectory_Failure() throws Exception { + File dir = new File(System.getProperty("user.dir")); + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + File tmpFile = new File(dir, "tmpFailure"); + assertFalse(tmpFile.exists()); + try { + tmpFile.createNewFile(); + assertTrue(tmpFile.exists()); + assertTrue(tmpFile.isFile()); + Lock lock = null; + try { + lock = new Lock(tmpFile); + boolean created = FileHelper.createTempDirectory(tmpFile); + if (Installation.isWindows()) { + // locking currently only effective on windows + assertFalse(created); + } else { + // change if there is a locking mechanism + assertTrue(created); + } + } finally { + if (lock != null) { + lock.release(); + } + } + } finally { + if (tmpFile.exists()) { + assertTrue(tmpFile.delete()); + } + } + } + + public void testRmdir() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + assertTrue(FileHelper.rmdir(dir)); + } + + public void testRmdir_Failure() throws Exception { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + Lock lock = null; + try { + lock = new Lock(jython_bat); + boolean removed = FileHelper.rmdir(dir); + if (Installation.isWindows()) { + // locking currently only effective on windows + assertFalse(removed); + } else { + // change if there is a locking mechanism + assertTrue(removed); + } + } finally { + if (lock != null) { + lock.release(); + } + assertTrue(FileHelper.rmdir(dir)); + } + } + + public void testReadAll() throws Exception { + File file = File.createTempFile("testReadAll", ""); + final String contents = new String("line1 \n line2 \n"); + FileHelper.write(file, contents); + String readContents = FileHelper.readAll(file); + assertEquals(contents, readContents); + } + + public void testReadAll_InputStream() throws Exception { + URL url = FileHelper.getRelativeURL(Autotest.class, JYTHON_TEST_TEMPLATE); + assertNotNull(url); + URI uri = new URI(url.toString()); + File file = new File(uri); + assertNotNull(file); + assertTrue(file.exists()); + String expectedContents = FileHelper.readAll(file); + InputStream is = FileHelper.getRelativeURLAsStream(Autotest.class, JYTHON_TEST_TEMPLATE); + assertNotNull(is); + String contents = FileHelper.readAll(is); + assertEquals(expectedContents, contents); + // now from a .jar + is = FileHelper.getRelativeURLAsStream(TestSuiteLoader.class, LOGO_GIF); + assertNotNull(is); + contents = FileHelper.readAll(is); + assertNotNull(contents); + assertEquals(964, contents.length()); + assertTrue(contents.startsWith("GIF89a&")); + } + + public void testReadAll_NonExisting() { + String readContents = null; + try { + readContents = FileHelper.readAll(new File("_non_existing")); + fail("FileNotFoundException expected"); + } catch (IOException e) { + assertNull(readContents); + } + } + + public void testGetRelativeURL() { + URL url = FileHelper.getRelativeURL(Installation.class, JYTHON_SMALL_C_PNG); + assertNotNull(url); + assertTrue(url.getPath().endsWith("org/python/util/install/".concat(JYTHON_SMALL_C_PNG))); + // now from a .jar + url = FileHelper.getRelativeURL(TestSuiteLoader.class, LOGO_GIF); + assertNotNull(url); + assertTrue(url.getPath().endsWith("!/junit/runner/".concat(LOGO_GIF))); + } + + public void testGetRelativeURLAsStream() throws IOException { + InputStream is = FileHelper.getRelativeURLAsStream(Installation.class, JYTHON_SMALL_C_PNG); + assertNotNull(is); + try { + assertTrue(is.read() >= 0); + } finally { + is.close(); + } + // now from a .jar + is = FileHelper.getRelativeURLAsStream(TestSuiteLoader.class, LOGO_GIF); + assertNotNull(is); + try { + assertTrue(is.read() >= 0); + } finally { + is.close(); + } + } + + public void testWrite() throws IOException { + File file = new File("testWrite"); + assertFalse(file.exists()); + try { + final String contents = new String("line1 \n line2 \n"); + FileHelper.write(file, contents); + assertTrue(file.exists()); + String readContents = FileHelper.readAll(file); + assertEquals(contents, readContents); + } finally { + if (file.exists()) { + assertTrue(file.delete()); + } + } + } + + public void testWrite_Existing() throws IOException { + File file = File.createTempFile("testWrite", ""); + assertTrue(file.exists()); + final String firstContents = "first dummy contents"; + FileHelper.write(file, firstContents); + assertEquals(firstContents, FileHelper.readAll(file)); + final String realContents = new String("line1 \n line2 \n"); + FileHelper.write(file, realContents); + assertEquals(realContents, FileHelper.readAll(file)); + } + + /** + * A poor man's file lock (does work on windows only) + */ + public static class Lock { + + private final FileOutputStream _fos; + + /** + * acquire a file lock + * + * @param file + * The file to be locked + * @throws FileNotFoundException + */ + public Lock(File file) throws FileNotFoundException { + _fos = new FileOutputStream(file); + } + + /** + * release the file lock + * + * @throws IOException + */ + public void release() throws IOException { + if (_fos != null) { + _fos.close(); + } + } + } +} diff --git a/installer/test/java/org/python/util/install/FrameInstallerTest.java b/installer/test/java/org/python/util/install/FrameInstallerTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/FrameInstallerTest.java @@ -0,0 +1,88 @@ +package org.python.util.install; + +import org.python.util.install.Installation.JavaVersionInfo; + +import junit.framework.TestCase; + +public class FrameInstallerTest extends TestCase { + + public void testInitDefaultJava() { + FrameInstaller.initDefaultJava(); + JavaVersionInfo vInfo = FrameInstaller.getJavaVersionInfo(); + assertNotNull(vInfo); + String version = vInfo.getVersion(); + assertNotNull(version); + assertTrue(version.length() > 0); + String specificationVersion = vInfo.getSpecificationVersion(); + assertNotNull(specificationVersion); + assertTrue(specificationVersion.length() > 0); + String vendor = vInfo.getVendor(); + assertNotNull(vendor); + assertTrue(vendor.length() > 0); + } + + public void testJavaVersionInfo() { + String version = "1;2;3"; + String vendor = "jython [macrosystems]"; + String specificationVersion = "@spec 1,4"; + + JavaVersionInfo vInfo = new JavaVersionInfo(); + vInfo.setVersion(version); + vInfo.setVendor(vendor); + vInfo.setSpecificationVersion(specificationVersion); + + FrameInstaller.setJavaVersionInfo(vInfo); + JavaVersionInfo returnedInfo = FrameInstaller.getJavaVersionInfo(); + + assertNotNull(returnedInfo); + assertEquals(version, returnedInfo.getVersion()); + assertEquals(vendor, returnedInfo.getVendor()); + assertEquals(specificationVersion, returnedInfo.getSpecificationVersion()); + } + + public void testInstallationType() { + InstallationType installationType = new InstallationType(); + installationType.addLibraryModules(); + installationType.removeDemosAndExamples(); + installationType.removeDocumentation(); + installationType.addSources(); + + FrameInstaller.setInstallationType(installationType); + InstallationType returnedType = FrameInstaller.getInstallationType(); + + assertNotNull(returnedType); + assertTrue(returnedType.installLibraryModules()); + assertFalse(returnedType.installDemosAndExamples()); + assertFalse(returnedType.installDocumentation()); + assertTrue(returnedType.installSources()); + } + + public void testStandalone() { + InstallationType installationType = new InstallationType(); + installationType.setStandalone(); + assertTrue(installationType.installLibraryModules()); + assertFalse(installationType.installDemosAndExamples()); + assertFalse(installationType.installDocumentation()); + assertFalse(installationType.installSources()); + + FrameInstaller.setInstallationType(installationType); + InstallationType returnedType = FrameInstaller.getInstallationType(); + + assertNotNull(returnedType); + assertTrue(returnedType.isStandalone()); + assertTrue(returnedType.installLibraryModules()); + assertFalse(returnedType.installDemosAndExamples()); + assertFalse(returnedType.installDocumentation()); + assertFalse(returnedType.installSources()); + } + + public void testSetGetJavaHomeHandler() { + assertNotNull(FrameInstaller.getJavaHomeHandler()); + JavaHomeHandler handler1 = new JavaHomeHandler(); + JavaHomeHandler handler2 = new JavaHomeHandler("some/dir"); + FrameInstaller.setJavaHomeHandler(handler1); + assertEquals(handler1, FrameInstaller.getJavaHomeHandler()); + FrameInstaller.setJavaHomeHandler(handler2); + assertEquals(handler2, FrameInstaller.getJavaHomeHandler()); + } +} diff --git a/installer/test/java/org/python/util/install/InstallationTest.java b/installer/test/java/org/python/util/install/InstallationTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/InstallationTest.java @@ -0,0 +1,107 @@ +package org.python.util.install; + +import java.io.File; + +import org.python.util.install.Installation.JavaVersionInfo; + +import junit.framework.TestCase; + +public class InstallationTest extends TestCase { + + public void testGetExternalJavaVersion() { + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.NORMAL_RETURN, versionInfo.getErrorCode()); + assertEquals("", versionInfo.getReason()); + assertTrue(versionInfo.getVersion().length() > 0); + assertTrue(versionInfo.getSpecificationVersion().length() > 0); + assertTrue(versionInfo.getVersion().startsWith(versionInfo.getSpecificationVersion())); + assertNotNull(versionInfo.getVendor()); + assertNotSame("", versionInfo.getVendor()); + } + + public void testGetExternalJavaVersionWithError() { + JavaHomeHandler javaHomeHandler = new JavaHomeHandler("non_existing/home"); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.ERROR_RETURN, versionInfo.getErrorCode()); + String reason = versionInfo.getReason(); + assertTrue(reason.indexOf("invalid") >= 0); + } + + public void testGetExternalJavaVersionNoBinDirectory() { + File wrongHome = new File(System.getProperty("user.home")); + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(wrongHome.getAbsolutePath()); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.ERROR_RETURN, versionInfo.getErrorCode()); + String reason = versionInfo.getReason(); + assertTrue(reason.indexOf("invalid") >= 0); + } + + public void testGetExternalJavaVersionNoJavaInBinDirectory() { + File wrongHome = new File(System.getProperty("user.home")); + File binDir = new File(wrongHome, "bin"); + assertFalse(binDir.exists()); + try { + assertTrue(binDir.mkdirs()); + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(wrongHome.getAbsolutePath()); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + assertEquals(Installation.ERROR_RETURN, versionInfo.getErrorCode()); + assertTrue(versionInfo.getReason().indexOf("invalid") >= 0); + } finally { + if (binDir.exists()) { + binDir.delete(); + } + } + } + + public void testIsValidJavaVersion() { + JavaVersionInfo javaVersionInfo = new JavaVersionInfo(); + + javaVersionInfo.setSpecificationVersion("1.1.9"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.2"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.3"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.4"); + assertFalse(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.5"); + assertTrue(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.6"); + assertTrue(Installation.isValidJava(javaVersionInfo)); + javaVersionInfo.setSpecificationVersion("1.7"); + assertTrue(Installation.isValidJava(javaVersionInfo)); + } + + public void testGetJavaSpecificationVersion() { + String specificationVersion = "1.4.2"; + assertEquals(14, Installation.getJavaSpecificationVersion(specificationVersion)); + specificationVersion = "1.5.0"; + assertEquals(15, Installation.getJavaSpecificationVersion(specificationVersion)); + specificationVersion = "1.6.0"; + assertEquals(16, Installation.getJavaSpecificationVersion(specificationVersion)); + } + + public void testIsGNUJava() { + assertFalse(Installation.isGNUJava()); + String originalVmName = System.getProperty(Installation.JAVA_VM_NAME); + try { + // fake GNU java + System.setProperty(Installation.JAVA_VM_NAME, "GNU libgcj"); + assertTrue(Installation.isGNUJava()); + } finally { + System.setProperty(Installation.JAVA_VM_NAME, originalVmName); + assertFalse(Installation.isGNUJava()); + } + } + + public void testGetDefaultJavaVersion() { + JavaVersionInfo info = Installation.getDefaultJavaVersion(); + assertNotNull(info); + assertEquals(Installation.NORMAL_RETURN, info.getErrorCode()); + String specVersion = info.getSpecificationVersion(); + assertNotNull(specVersion); + assertTrue(specVersion.length() >= 3); + } + +} diff --git a/installer/test/java/org/python/util/install/InstallationTypeTest.java b/installer/test/java/org/python/util/install/InstallationTypeTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/InstallationTypeTest.java @@ -0,0 +1,119 @@ +package org.python.util.install; + +import junit.framework.TestCase; + +// test checkin +public class InstallationTypeTest extends TestCase { + + private InstallationType _type; + + protected void setUp() { + _type = new InstallationType(); + } + + public void testConstruction() { + assertTrue(_type.isStandard()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testStandard() { + _type.setStandard(); + assertTrue(_type.isStandard()); + assertTrue(_type.installLibraryModules()); + assertTrue(_type.installDemosAndExamples()); + assertTrue(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testMinimum() { + assertFalse(_type.isMinimum()); + _type.setMinimum(); + assertTrue(_type.isMinimum()); + assertFalse(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testAll() { + assertFalse(_type.isAll()); + _type.setAll(); + assertTrue(_type.isAll()); + assertTrue(_type.installLibraryModules()); + assertTrue(_type.installDemosAndExamples()); + assertTrue(_type.installDocumentation()); + assertTrue(_type.installSources()); + assertFalse(_type.isStandalone()); + assertTrue(_type.isPredefined()); + } + + public void testStandalone() { + assertFalse(_type.isStandalone()); + _type.setStandalone(); + assertTrue(_type.isStandalone()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertTrue(_type.isPredefined()); + + // sure to handle this as follows? + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + } + + public void testAddRemove() { + _type.removeDocumentation(); + assertTrue(_type.installLibraryModules()); + assertTrue(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + + _type.removeDemosAndExamples(); + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertFalse(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + + _type.addSources(); + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertFalse(_type.installDocumentation()); + assertTrue(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + + _type.addDocumentation(); + assertTrue(_type.installLibraryModules()); + assertFalse(_type.installDemosAndExamples()); + assertTrue(_type.installDocumentation()); + assertTrue(_type.installSources()); + assertFalse(_type.isMinimum()); + assertFalse(_type.isStandard()); + assertFalse(_type.isAll()); + assertFalse(_type.isStandalone()); + assertFalse(_type.isPredefined()); + } + +} diff --git a/installer/test/java/org/python/util/install/InstallerCommandLineTest.java b/installer/test/java/org/python/util/install/InstallerCommandLineTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/InstallerCommandLineTest.java @@ -0,0 +1,637 @@ +package org.python.util.install; + +import java.io.File; + +import junit.framework.TestCase; + +public class InstallerCommandLineTest extends TestCase { + + public void testValidArguments() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasArguments()); + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasArguments()); + + args = new String[] { "-c" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--directory", "c:/temp" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--type", "all" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-t", "minimum" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-type", "standard" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-v" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--verbose" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-A" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--autotest" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasArguments()); + } + + public void testInvalidArguments() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "--one" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--one argOne" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "--one", "--two", "--three" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-o" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-type" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-type", "weird" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + assertTrue(commandLine.hasArguments()); + + args = new String[] { "-" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testMissingArgument() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "--directory" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--type" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testUnknownArgument() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "yeah" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "yeah" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "yeah", "yoyo" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--type", "takatuka" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testOptionGroups() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-s", "-c" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-s", "-A" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-c", "-A" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "--console" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--silent", "--autotest" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "--console", "--autotest" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-?", "-h" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + + args = new String[] { "-?", "--help" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testSilent() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-s" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); // expect required directory in silent mode + + args = new String[] { "-s", "-d", "/tmp" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + } + + public void testConsole() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-c" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasConsoleOption()); + } + + public void testGui() { + String[] args; + InstallerCommandLine commandLine; + + // normal gui startup without any arguments + assertTrue(Installation.isGuiAllowed()); + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasConsoleOption()); + assertFalse(commandLine.hasSilentOption()); + } + + /** + * simulate startup on a headless system (auto-switch to console mode) + */ + public void testHeadless() { + String[] args; + InstallerCommandLine commandLine; + boolean originalHeadless = Boolean.getBoolean(Installation.HEADLESS_PROPERTY_NAME); + try { + if (!originalHeadless) { + System.setProperty(Installation.HEADLESS_PROPERTY_NAME, "true"); + } + assertFalse(Installation.isGuiAllowed()); + + // without any arguments + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasConsoleOption()); // auto switch + assertFalse(commandLine.hasSilentOption()); + + // with one argument + args = new String[] {"-v"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); // auto switch + assertFalse(commandLine.hasSilentOption()); + + // with more arguments + args = new String[] {"-v", "-t", "minimum" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); // auto switch + assertFalse(commandLine.hasSilentOption()); + assertTrue(commandLine.hasTypeOption()); + InstallationType type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isMinimum()); + + // silent should override! + args = new String[] {"-v", "-s", "-d", "some_dir"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertFalse(commandLine.hasConsoleOption()); // no auto switch + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + File dir = commandLine.getTargetDirectory(); + assertNotNull(dir); + assertEquals("some_dir", dir.getName()); + + // -A (autotest) should override as well + args = new String[] {"-A"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasVerboseOption()); + assertFalse(commandLine.hasConsoleOption()); // no auto switch + assertFalse(commandLine.hasSilentOption()); + + // console aready present should be no problem + args = new String[] {"-c", "-v"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); + assertFalse(commandLine.hasSilentOption()); + } finally { + if (!originalHeadless) { + System.setProperty(Installation.HEADLESS_PROPERTY_NAME, "false"); + assertTrue(Installation.isGuiAllowed()); + } + } + } + + public void testGNUSwitchToConsole() { + String originalVmName = System.getProperty(Installation.JAVA_VM_NAME); + try { + // fake GNU java + System.setProperty(Installation.JAVA_VM_NAME, "GNU libgcj"); + assertTrue(Installation.isGNUJava()); + String[] args; + InstallerCommandLine commandLine; + // expect auto switch + args = new String[] {"-v"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasVerboseOption()); + assertTrue(commandLine.hasConsoleOption()); // auto switch + // expect no auto switch + args = new String[] {"-s", "-d", "some_dir"}; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertFalse(commandLine.hasVerboseOption()); + assertFalse(commandLine.hasConsoleOption()); // no auto switch + assertTrue(commandLine.hasDirectoryOption()); + File dir = commandLine.getTargetDirectory(); + assertNotNull(dir); + assertEquals("some_dir", dir.getName()); + } finally { + System.setProperty(Installation.JAVA_VM_NAME, originalVmName); + assertFalse(Installation.isGNUJava()); + } + } + + + public void testDirectory() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[] { "-d", "dir" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + + args = new String[] { "-s", "--directory", "dir" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + } + + public void testType() { + String[] args; + InstallerCommandLine commandLine; + InstallationType type; + + args = new String[] { "-t", "all" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isAll()); + + args = new String[] { "--type", "standard" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isStandard()); + + args = new String[] { "--type", "minimum" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isMinimum()); + + args = new String[] { "--type", "standalone" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasTypeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertTrue(type.isStandalone()); + + assertFalse(commandLine.getJavaHomeHandler().isDeviation()); + } + + public void testInclude() { + String[] args; + InstallerCommandLine commandLine; + InstallationType type; + + args = new String[] { "-t", "minimum", "-i", "mod" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "-t", "minimum", "-i", "mod", "demo" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "-t", "minimum", "-i", "mod", "demo", "doc" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "-t", "minimum", "--include", "mod", "demo", "doc", "src" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasIncludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertTrue(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-i", "modulo" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testExclude() { + String[] args; + InstallerCommandLine commandLine; + InstallationType type; + + args = new String[] { "-t", "all", "-e", "mod" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertTrue(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-t", "all", "-e", "mod", "demo" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertTrue(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-t", "all", "-e", "mod", "demo", "doc" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertTrue(type.installSources()); + + args = new String[] { "-t", "all", "--exclude", "mod", "demo", "doc", "src" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasExcludeOption()); + type = commandLine.getInstallationType(); + assertNotNull(type); + assertFalse(type.isStandalone()); + assertFalse(type.installLibraryModules()); + assertFalse(type.installDemosAndExamples()); + assertFalse(type.installDocumentation()); + assertFalse(type.installSources()); + + args = new String[] { "--exclude", "sources" }; + commandLine = new InstallerCommandLine(); + assertFalse(commandLine.setArgs(args)); + } + + public void testJavaHomeHandler() { + String[] args; + InstallerCommandLine commandLine; + final String javaHomeName = "java/home/dir"; + + args = new String[] { "-j", javaHomeName }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasJavaHomeOption()); + JavaHomeHandler javaHomeHandler = commandLine.getJavaHomeHandler(); + assertNotNull(javaHomeHandler); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + + args = new String[] { "--jre", javaHomeName }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasJavaHomeOption()); + javaHomeHandler = commandLine.getJavaHomeHandler(); + assertNotNull(javaHomeHandler); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + + assertNull(commandLine.getTargetDirectory()); + } + + public void testExamples() { + String[] args; + InstallerCommandLine commandLine; + final String javaHomeName = "java/home/dir"; + + args = new String[] { "-c" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasConsoleOption()); + + args = new String[] { "-s", "-d", "dir" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + + args = new String[] { "-s", "-d", "dir", "-t", "standard", "-j", javaHomeName, "-v" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + assertTrue(commandLine.hasTypeOption()); + assertNotNull(commandLine.getInstallationType()); + assertTrue(commandLine.getInstallationType().installDemosAndExamples()); + assertTrue(commandLine.getInstallationType().installDocumentation()); + assertTrue(commandLine.getInstallationType().installLibraryModules()); + assertFalse(commandLine.getInstallationType().installSources()); + assertTrue(commandLine.hasJavaHomeOption()); + JavaHomeHandler javaHomeHandler = commandLine.getJavaHomeHandler(); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + assertTrue(commandLine.hasVerboseOption()); + + args = new String[] { "-s", "-d", "dir", "-t", "standard", "-e", "doc", "demo", "-i", "src", "-j", javaHomeName, "-v" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasSilentOption()); + assertTrue(commandLine.hasDirectoryOption()); + assertNotNull(commandLine.getTargetDirectory()); + assertEquals("dir", commandLine.getTargetDirectory().getName()); + assertTrue(commandLine.hasTypeOption()); + assertNotNull(commandLine.getInstallationType()); + assertFalse(commandLine.getInstallationType().installDemosAndExamples()); + assertFalse(commandLine.getInstallationType().installDocumentation()); + assertTrue(commandLine.getInstallationType().installLibraryModules()); + assertTrue(commandLine.getInstallationType().installSources()); + assertTrue(commandLine.hasJavaHomeOption()); + javaHomeHandler = commandLine.getJavaHomeHandler(); + assertTrue(javaHomeHandler.toString().indexOf(javaHomeName) >= 0); + assertTrue(javaHomeHandler.isDeviation()); + assertTrue(commandLine.hasVerboseOption()); + } + + public void testHelp() { + String[] args; + InstallerCommandLine commandLine; + + args = new String[0]; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertFalse(commandLine.hasHelpOption()); + + args = new String[] { "--help" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasHelpOption()); + + args = new String[] { "-h" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasHelpOption()); + + args = new String[] { "-?" }; + commandLine = new InstallerCommandLine(); + assertTrue(commandLine.setArgs(args)); + assertTrue(commandLine.hasHelpOption()); + + // now print the help + commandLine.printHelp(); + } + + public void testHasVerboseOptionInArgs() { + String[] args = new String[0]; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "b", "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", InstallerCommandLine.VERBOSE_SHORT, "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "-" + InstallerCommandLine.VERBOSE_SHORT, "c"}; + assertTrue(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", InstallerCommandLine.VERBOSE_LONG, "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "-" + InstallerCommandLine.VERBOSE_LONG, "c"}; + assertFalse(InstallerCommandLine.hasVerboseOptionInArgs(args)); + + args = new String[] {"a", "--" + InstallerCommandLine.VERBOSE_LONG, "c"}; + assertTrue(InstallerCommandLine.hasVerboseOptionInArgs(args)); + } + +} diff --git a/installer/test/java/org/python/util/install/JavaHomeHandlerTest.java b/installer/test/java/org/python/util/install/JavaHomeHandlerTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/JavaHomeHandlerTest.java @@ -0,0 +1,114 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; + +import junit.framework.TestCase; + +public class JavaHomeHandlerTest extends TestCase { + + private static final String JAVA_HOME = JavaHomeHandler.JAVA_HOME; + + private static final String JAVA = "java"; + + private static final String SOME_WEIRD_HOME = "some/weird/home"; + + private String _originalJavaHome; + + @Override + protected void setUp() throws Exception { + JavaHomeHandler.reset(); + _originalJavaHome = System.getProperty(JAVA_HOME); + } + + @Override + protected void tearDown() throws Exception { + System.setProperty(JAVA_HOME, _originalJavaHome); + } + + public void testGetExecutableName() throws IOException { + String executable = new JavaHomeHandler().getExecutableName(); + assertNotNull(executable); + assertTrue(executable.length() > JAVA.length()); + String homePath = createTempHome().getAbsolutePath(); + executable = new JavaHomeHandler(homePath).getExecutableName(); + assertTrue(executable.length() > JAVA.length()); + assertTrue(executable.indexOf(homePath) >= 0); + System.setProperty(JAVA_HOME, homePath); + executable = new JavaHomeHandler().getExecutableName(); + assertTrue(executable.length() > JAVA.length()); + assertTrue(executable.indexOf(homePath) >= 0); + } + + public void testGetExecutableName_NonExisting() { + String executable = new JavaHomeHandler(SOME_WEIRD_HOME).getExecutableName(); + assertEquals(JAVA, executable); // fallback + System.setProperty(JAVA_HOME, SOME_WEIRD_HOME); + executable = new JavaHomeHandler().getExecutableName(); + assertEquals(JAVA, executable); // fallback + } + + public void testCreateJavaHomeHandler() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(); + assertNotNull(handler); + System.setProperty(JAVA_HOME, SOME_WEIRD_HOME); + handler = new JavaHomeHandler(); + assertNotNull(handler); + System.setProperty(JAVA_HOME, createTempHome().getAbsolutePath()); + handler = new JavaHomeHandler(); + assertNotNull(handler); + } + + public void testCreateHandler_Deviation() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(SOME_WEIRD_HOME); + assertNotNull(handler); + handler = new JavaHomeHandler(createTempHome().getAbsolutePath()); + assertNotNull(handler); + } + + public void testIsDeviation() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(createTempHome().getAbsolutePath()); + assertTrue(handler.isDeviation()); + handler = new JavaHomeHandler(); + assertFalse(handler.isDeviation()); + handler = new JavaHomeHandler(System.getProperty(JAVA_HOME)); + assertFalse(handler.isDeviation()); + } + + public void testGetJavaHome() throws IOException { + String tempHome = createTempHome().getAbsolutePath(); + JavaHomeHandler handler = new JavaHomeHandler(tempHome); + String home = handler.getHome().getAbsolutePath(); + assertEquals(tempHome, home); + try { + handler = new JavaHomeHandler(SOME_WEIRD_HOME); + } catch (InstallerException ie) { + assertEquals("no valid java home", ie.getMessage()); + } + } + + public void testIsValidJavaHome() throws IOException { + JavaHomeHandler handler = new JavaHomeHandler(SOME_WEIRD_HOME); + assertFalse(handler.isValidHome()); + handler = new JavaHomeHandler(); + assertTrue(handler.isValidHome()); + handler = new JavaHomeHandler(createTempHome().getAbsolutePath()); + assertTrue(handler.isValidHome()); + } + + private File createTempHome() throws IOException { + File home = File.createTempFile("JavaHomeHandler", "Test"); + assertTrue(FileHelper.createTempDirectory(home)); + File binDir = new File(home, "bin"); + assertTrue(binDir.mkdirs()); + String executableName = JAVA; + if (Installation.isWindows()) { + executableName = executableName.concat(".exe"); + } + File java = new File(binDir, executableName); + FileHelper.write(java, "dummy"); + assertTrue(java.exists()); + assertTrue(java.isFile()); + return home; + } +} diff --git a/installer/test/java/org/python/util/install/JavaTest_Standalone.java b/installer/test/java/org/python/util/install/JavaTest_Standalone.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/JavaTest_Standalone.java @@ -0,0 +1,32 @@ +package org.python.util.install; + +import org.python.util.install.Installation.JavaVersionInfo; + +/** + * Helper class to test an external java version + */ +public class JavaTest_Standalone { + + public static void main(String[] args) { + if (args.length > 0) { + JavaHomeHandler javaHomeHandler = new JavaHomeHandler(args[0]); + JavaVersionInfo versionInfo = Installation.getExternalJavaVersion(javaHomeHandler); + if (versionInfo.getErrorCode() != Installation.NORMAL_RETURN) { + System.err.println(versionInfo.getReason()); + } else { + System.out.println(getPrefix() + "java version:" + versionInfo.getVersion()); + System.out.println(getPrefix() + "java spec version:" + versionInfo.getSpecificationVersion()); + } + System.exit(versionInfo.getErrorCode()); + } else { + System.err.println(getPrefix() + "missing argument: please specify the java home directory " + + "(/bin directory assumed below)"); + System.exit(1); + } + } + + private static String getPrefix() { + return "[JavaTest_Standalone] "; + } + +} diff --git a/installer/test/java/org/python/util/install/StandalonePackagerTest.java b/installer/test/java/org/python/util/install/StandalonePackagerTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/StandalonePackagerTest.java @@ -0,0 +1,183 @@ +package org.python.util.install; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import junit.framework.TestCase; + +public class StandalonePackagerTest extends TestCase { + private File _sourceJarFile; + private File _targetJarFile; + + private File _contentFile; + private File _nextContentFile; + private File _additionalFile; + + private File _contentDir; + private File _additionalDir; + + private File _jarDir; + + protected void setUp() throws Exception { + _jarDir = File.createTempFile("jarDir", ""); + assertTrue(FileHelper.createTempDirectory(_jarDir)); + _targetJarFile = new File(_jarDir, "target.jar"); + _sourceJarFile = new File(_jarDir, "source.jar"); + + _contentDir = File.createTempFile("content", ""); + assertTrue(FileHelper.createTempDirectory(_contentDir)); + _contentFile = new File(_contentDir, "content.file"); + _contentFile.createNewFile(); + assertTrue(_contentFile.exists()); + + createSourceJar(); + } + + protected void tearDown() throws Exception { + if (_sourceJarFile != null) { + _sourceJarFile.delete(); + } + if (_targetJarFile != null) { + _targetJarFile.delete(); + } + if (_contentFile != null) { + _contentFile.delete(); + } + if (_nextContentFile != null) { + _nextContentFile.delete(); + } + if (_additionalFile != null) { + _additionalFile.delete(); + } + if (_contentDir != null) { + _contentDir.delete(); + } + if (_additionalDir != null) { + _additionalDir.delete(); + } + if (_jarDir != null) { + _jarDir.delete(); + } + } + + /** + * test the static method emptyDir() + */ + public void testEmptyDir() throws Exception { + File tempContentFile = new File(_contentDir, "temp"); + tempContentFile.createNewFile(); + assertTrue(tempContentFile.exists()); + File tempDir = new File(_contentDir, "tempDir"); + assertTrue(FileHelper.createTempDirectory(tempDir)); + + StandalonePackager.emptyDirectory(_contentDir, _contentFile); + assertTrue(_contentFile.exists()); + assertFalse(tempContentFile.exists()); + assertFalse(tempDir.exists()); + assertEquals(1, _contentDir.list().length); + } + + /** + * test adding a jar file, a directory, and another single file + */ + public void testAdd_Jar_Directory_File() throws IOException { + createAdditionalDirectory(); + _nextContentFile = File.createTempFile("nextContent.file", ""); + _nextContentFile.createNewFile(); + assertTrue(_nextContentFile.exists()); + + StandalonePackager packager = new StandalonePackager(_targetJarFile); + try { + packager.addJarFile(_sourceJarFile); + packager.addFullDirectory(_additionalDir); + packager.addFile(_nextContentFile, null); + } finally { + packager.close(); + } + + assertTrue(_targetJarFile.exists()); + + Map mandatoryEntries = new HashMap(8); + mandatoryEntries.put(_contentDir.getName(), "dir"); + mandatoryEntries.put(_contentFile.getName(), "file"); + mandatoryEntries.put(_nextContentFile.getName(), "file"); + mandatoryEntries.put(_additionalDir.getName(), "dir"); + mandatoryEntries.put(_additionalFile.getName(), "file"); + mandatoryEntries.put("META-INF", "dir"); + mandatoryEntries.put("MANIFEST.MF", "file"); + + JarFile targetJarFile = new JarFile(_targetJarFile); + try { + Enumeration entries = targetJarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = (JarEntry) entries.nextElement(); + String name; + String entryName = entry.getName(); + int slashIndex = entryName.indexOf("/"); + if (slashIndex >= 0) { + // handle directory + name = entryName.substring(slashIndex + 1); + String dirName = entryName.substring(0, slashIndex); + assertTrue(mandatoryEntries.containsKey(dirName)); + assertEquals("dir", mandatoryEntries.get(dirName)); + mandatoryEntries.remove(dirName); + } else { + name = entryName; + } + if (mandatoryEntries.containsKey(name)) { + assertEquals("file", (String) mandatoryEntries.get(name)); + assertFalse(entry.isDirectory()); + mandatoryEntries.remove(name); + } + } + assertTrue(mandatoryEntries.size() == 0); + assertNotNull(targetJarFile.getManifest()); + } finally { + targetJarFile.close(); + } + } + + private void createSourceJar() throws FileNotFoundException, IOException { + Manifest manifest = new Manifest(); + JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(_sourceJarFile), manifest); + addFile(_contentFile, _contentDir, jarOut); + jarOut.close(); + } + + private void createAdditionalDirectory() throws IOException { + _additionalDir = File.createTempFile("additional", ""); + assertTrue(FileHelper.createTempDirectory(_additionalDir)); + + _additionalFile = new File(_additionalDir, "additional.file"); + _additionalFile.createNewFile(); + assertTrue(_additionalFile.exists()); + } + + private void addFile(File file, File parentDir, JarOutputStream jarOut) throws IOException { + byte[] buffer = new byte[1024]; + FileInputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + String jarEntryName = parentDir.getName() + "/" + file.getName(); + jarOut.putNextEntry(new JarEntry(jarEntryName)); + for (int read = 0; read != -1; read = inputStream.read(buffer)) + jarOut.write(buffer, 0, read); + jarOut.closeEntry(); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + +} diff --git a/installer/test/java/org/python/util/install/StartScriptGeneratorTest.java b/installer/test/java/org/python/util/install/StartScriptGeneratorTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/StartScriptGeneratorTest.java @@ -0,0 +1,277 @@ +package org.python.util.install; + +import java.io.File; +import java.io.IOException; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +public class StartScriptGeneratorTest extends TestCase { + + private static final String AT_DATE = "@DATE"; + + private static final String WIN_CR_LF = StartScriptGenerator.WIN_CR_LF; + + private StartScriptGenerator _generator; + + private File _targetDir; + + protected void setUp() throws Exception { + String userDirName = System.getProperty("user.dir"); // only true in eclipse ? + File userDir = new File(userDirName); + File parentDir = userDir.getParentFile(); + assertTrue(parentDir.exists()); + _targetDir = new File(parentDir, "jython"); + if (!_targetDir.exists()) { + _targetDir = new File(parentDir, "jython-trunk"); + } + assertTrue(_targetDir.exists()); + assertTrue(_targetDir.isDirectory()); + _targetDir = new File(_targetDir, "src"); + _targetDir = new File(_targetDir, "shell"); + assertTrue(_targetDir.exists()); + assertTrue(_targetDir.isDirectory()); + _generator = new StartScriptGenerator(_targetDir, new JavaHomeHandler()); + } + + // TODO: test on Solaris + public void testUnix() throws IOException { + _generator.setFlavour(StartScriptGenerator.UNIX_FLAVOUR); + StringBuffer buf = new StringBuffer(100); + buf.append("#!/usr/bin/env bash\n"); + buf.append("\n"); + buf.append("# This file was generated by the Jython installer\n"); + buf.append("# Created on " + AT_DATE + " by " + System.getProperty("user.name") + "\n"); + buf.append("\n"); + buf.append("JAVA_HOME=\""); + buf.append(System.getProperty("java.home")); + buf.append("\"\n"); + buf.append("JYTHON_HOME_FALLBACK=\""); + buf.append(_targetDir.getAbsolutePath()); + buf.append("\"\n"); + // some rudimentary tests - feel free to do more + String start = buf.toString().replaceAll(AT_DATE, new Date().toString()); + String unixScript = _generator.getJythonScript(StartScriptGenerator.UNIX_FLAVOUR); + assertTrue(unixScript.startsWith(start)); + assertTrue(unixScript.length() > 3500); + assertTrue(unixScript.indexOf("-Dpython.home=") > start.length()); + assertTrue(unixScript.indexOf("-Dpython.executable=") > start.length()); + // no hard coding of JYTHON_HOME + int jythonHomeIndex = unixScript.indexOf("if [ -z \"$JYTHON_HOME\" ] ; then"); + assertTrue(jythonHomeIndex >= 0); + int definitionIndex = unixScript.indexOf("JYTHON_HOME="); + assertTrue(definitionIndex > jythonHomeIndex || definitionIndex < 0); + } + + public void testWindows() throws IOException { + StringBuffer winBuf = new StringBuffer(100); + winBuf.append("@echo off" + WIN_CR_LF); + winBuf.append("rem This file was generated by the Jython installer" + WIN_CR_LF); + winBuf.append("rem Created on " + AT_DATE + " by " + System.getProperty("user.name") + "" + + WIN_CR_LF); + winBuf.append(WIN_CR_LF); + winBuf.append("set JAVA_HOME=\""); + winBuf.append(System.getProperty("java.home")); + winBuf.append("\""); + winBuf.append(WIN_CR_LF); + winBuf.append("set JYTHON_HOME_FALLBACK=\""); + winBuf.append(_targetDir.getAbsolutePath()); + winBuf.append("\""); + winBuf.append(WIN_CR_LF); + // some rudimentary tests - feel free to do more + String start = winBuf.toString().replaceAll(AT_DATE, new Date().toString()); + String winScript = _generator.getJythonScript(StartScriptGenerator.WINDOWS_FLAVOUR); + assertTrue(winScript.startsWith(start)); + assertTrue(winScript.length() > 3500); + assertTrue(winScript.indexOf("if not \"%_TRIMMED_JAVA_HOME%\"==\"\"") > start.length()); + assertTrue(winScript.indexOf("-Dpython.home=") > start.length()); + assertTrue(winScript.indexOf("-Dpython.executable=") > start.length()); + // no hard coding of JYTHON_HOME + int jythonHomeIndex = winScript.indexOf("if not \"%_TRIMMED_JYTHON_HOME%\"==\"\""); + assertTrue(jythonHomeIndex >= 0); + int definitionIndex = winScript.indexOf("set JYTHON_HOME="); + assertTrue(definitionIndex > jythonHomeIndex || definitionIndex < 0); + } + + public void testFlavour() { + int expectedFlavour; + expectedFlavour = StartScriptGenerator.UNIX_FLAVOUR; + _generator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, _generator.getFlavour()); + expectedFlavour = StartScriptGenerator.BOTH_FLAVOUR; + _generator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, _generator.getFlavour()); + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(new File("dummy"), + new JavaHomeHandler("dummy"), + false); + expectedFlavour = StartScriptGenerator.WINDOWS_FLAVOUR; + testGenerator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, testGenerator.getFlavour()); + expectedFlavour = StartScriptGenerator.UNIX_FLAVOUR; + testGenerator.setFlavour(expectedFlavour); + assertEquals(expectedFlavour, testGenerator.getFlavour()); + testGenerator = new TestStartScriptGenerator(new File("dummy"), + new JavaHomeHandler("dummy"), + true); + testGenerator.setFlavour(StartScriptGenerator.WINDOWS_FLAVOUR); + assertEquals(StartScriptGenerator.BOTH_FLAVOUR, testGenerator.getFlavour()); + } + + public void testWindowsFlavour() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + try { + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + // windows flavour + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(dir, + new JavaHomeHandler(), + false); + testGenerator.setFlavour(StartScriptGenerator.WINDOWS_FLAVOUR); + testGenerator.generateStartScripts(); + String[] fileNames = dir.list(); + int fileNamesLength = fileNames.length; + assertEquals(2, fileNamesLength); // 1 file plus the /bin subdirectory + HashSet fileNamesSet = new HashSet(2); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("bin")); + assertTrue(fileNamesSet.contains("jython.bat")); + fileNames = bin.list(); + assertEquals(1, fileNames.length); + assertEquals("jython.bat", fileNames[0]); + } finally { + if (dir.exists()) { + assertTrue("unable to delete directory ".concat(dir.getAbsolutePath()), + FileHelper.rmdir(dir)); + } + } + } + + public void testUnixFlavour() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + try { + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + // unix flavour + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(dir, + new JavaHomeHandler(), + false); + testGenerator.setFlavour(StartScriptGenerator.UNIX_FLAVOUR); + testGenerator.generateStartScripts(); + String[] fileNames = dir.list(); + int fileNamesLength = fileNames.length; + assertEquals(2, fileNamesLength); // 1 file plus the /bin subdirectory + HashSet fileNamesSet = new HashSet(2); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("bin")); + assertTrue(fileNamesSet.contains("jython")); + fileNames = bin.list(); + assertEquals(1, fileNames.length); + assertEquals("jython", fileNames[0]); + } finally { + if (dir.exists()) { + assertTrue("unable to delete directory ".concat(dir.getAbsolutePath()), + FileHelper.rmdir(dir)); + } + } + } + + public void testBothFlavours() throws IOException { + File dir = new File(System.getProperty("java.io.tmpdir"), "StartScriptGeneratorTest"); + try { + if (!dir.exists()) { + assertTrue(dir.mkdirs()); + } + File bin = new File(dir, "bin"); + if (!bin.exists()) { + assertTrue(bin.mkdirs()); + } + File jython = new File(bin, "jython"); + if (!jython.exists()) { + assertTrue(jython.createNewFile()); + } + File jython_bat = new File(bin, "jython.bat"); + if (!jython_bat.exists()) { + assertTrue(jython_bat.createNewFile()); + } + // both flavours + TestStartScriptGenerator testGenerator = new TestStartScriptGenerator(dir, + new JavaHomeHandler(), + true); + // test generator constructor timing problem: do set the flavour once again + testGenerator.setFlavour(StartScriptGenerator.WINDOWS_FLAVOUR); + testGenerator.generateStartScripts(); + String[] fileNames = dir.list(); + int fileNamesLength = fileNames.length; + assertEquals(3, fileNamesLength); // 2 files plus the /bin subdirectory + Set fileNamesSet = new HashSet(4); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("bin")); + assertTrue(fileNamesSet.contains("jython")); + assertTrue(fileNamesSet.contains("jython.bat")); + fileNames = bin.list(); + fileNamesLength = fileNames.length; + assertEquals(2, fileNamesLength); + fileNamesSet = new HashSet(4); + for (int i = 0; i < fileNamesLength; i++) { + fileNamesSet.add(fileNames[i]); + } + assertTrue(fileNamesSet.contains("jython")); + assertTrue(fileNamesSet.contains("jython.bat")); + } finally { + if (dir.exists()) { + assertTrue("unable to delete directory ".concat(dir.getAbsolutePath()), + FileHelper.rmdir(dir)); + } + } + } + + class TestStartScriptGenerator extends StartScriptGenerator { + + private boolean _hasBothFlavours; + + public TestStartScriptGenerator(File targetDirectory, + JavaHomeHandler javaHomeHandler, + boolean hasBothFlavours) { + super(targetDirectory, javaHomeHandler); + _hasBothFlavours = hasBothFlavours; + } + + protected boolean hasUnixlikeShell() { + return _hasBothFlavours; + } + } +} diff --git a/installer/test/java/org/python/util/install/UnicodeSequencesTest.java b/installer/test/java/org/python/util/install/UnicodeSequencesTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/UnicodeSequencesTest.java @@ -0,0 +1,34 @@ +package org.python.util.install; + +import java.util.HashSet; +import java.util.Set; + +import junit.framework.TestCase; + +public class UnicodeSequencesTest extends TestCase { + + private static Set _latin1Encodings; + + public void testUmlaute() { + String fileEncoding = System.getProperty("file.encoding", "unknown"); + if (getLatin1Encodings().contains(fileEncoding)) { + assertEquals("?", UnicodeSequences.a2); + assertEquals("?", UnicodeSequences.A2); + assertEquals("?", UnicodeSequences.o2); + assertEquals("?", UnicodeSequences.O2); + assertEquals("?", UnicodeSequences.u2); + assertEquals("?", UnicodeSequences.U2); + } + } + + private static Set getLatin1Encodings() { + if (_latin1Encodings == null) { + _latin1Encodings = new HashSet(3); + _latin1Encodings.add("ISO-LATIN-1"); + _latin1Encodings.add("ISO-8859-1"); + _latin1Encodings.add("Cp1252"); + } + return _latin1Encodings; + } + +} diff --git a/installer/test/java/org/python/util/install/driver/AutotestTest.java b/installer/test/java/org/python/util/install/driver/AutotestTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/AutotestTest.java @@ -0,0 +1,78 @@ +package org.python.util.install.driver; + +import java.io.File; + +import org.python.util.install.InstallerCommandLine; + +import junit.framework.TestCase; + +public class AutotestTest extends TestCase { + + private Autotest _autotest; + + protected void setUp() throws Exception { + InstallerCommandLine commandLine = new InstallerCommandLine(); + commandLine.setArgs(new String[0]); + _autotest = new SilentAutotest(commandLine); + } + + public void testCreateDirectories() { + File rootDir = Autotest.getRootDir(); + File targetDir = _autotest.getTargetDir(); + assertNotNull(rootDir); + verifyDir(rootDir, false); + assertNotNull(targetDir); + verifyDir(targetDir, true); + assertEquals(rootDir, targetDir.getParentFile()); + } + + public void testCommandLineArgs() { + String[] args = new String[] { "-x", "-y", "-z" }; + _autotest.setCommandLineArgs(args); + int len = _autotest.getCommandLineArgs().length; + assertEquals(args.length, len); + for (int i = 0; i < args.length; i++) { + assertEquals(args[i], _autotest.getCommandLineArgs()[i]); + } + } + + public void testAddArgument() { + String[] args = new String[] { "-x", "-y", "-z" }; + _autotest.setCommandLineArgs(args); + _autotest.addArgument("-u"); + assertEquals(args.length + 1, _autotest.getCommandLineArgs().length); + for (int i = 0; i < args.length; i++) { + assertEquals(args[i], _autotest.getCommandLineArgs()[i]); + } + assertEquals("-u", _autotest.getCommandLineArgs()[args.length]); + } + + public void testVerify() throws Exception { + TestVerifier testVerifier = new TestVerifier(); + _autotest.setVerifier(testVerifier); + assertNotNull(_autotest.getVerifier()); + assertNotNull(_autotest.getVerifier().getTargetDir()); + assertEquals(_autotest.getTargetDir(), testVerifier.getTargetDir()); + try { + _autotest.getVerifier().verify(); + fail("should have thrown"); + } catch (DriverException de) { + } + + } + + private void verifyDir(File dir, boolean ensureEmpty) { + assertTrue(dir.exists()); + assertTrue(dir.isDirectory()); + if (ensureEmpty) { + assertTrue(dir.listFiles().length <= 0); + } + } + + private static class TestVerifier extends NormalVerifier { + public void verify() throws DriverException { + throw new DriverException("test verification failure"); + } + } + +} diff --git a/installer/test/java/org/python/util/install/driver/DrivableConsole.java b/installer/test/java/org/python/util/install/driver/DrivableConsole.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/DrivableConsole.java @@ -0,0 +1,82 @@ +package org.python.util.install.driver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.python.util.install.driver.Tunnel; + +/** + * A simple class performing console I/O, easy to test. + */ +public class DrivableConsole { + + private static final String _PROMPT = ">>>"; + private Tunnel _tunnel; + + public DrivableConsole(Tunnel tunnel) { + _tunnel = tunnel; + } + + /** + * The console logic. + */ + public void handleConsoleIO() throws Exception { + String answer; + answer = question("first question"); + if ("1".equals(answer)) { + System.out.println("answer1 is " + answer); + answer = question("second question"); + if ("2".equals(answer)) { + System.out.println("answer2 is " + answer); + answer = question("third question"); + if ("3".equals(answer)) { + System.out.println("answer3 is " + answer); + } else { + throw new Exception("wrong answer3: " + answer); + } + } else { + throw new Exception("wrong answer2: " + answer); + } + } else { + throw new Exception("wrong answer1: " + answer); + } + } + + /** + * Write a question (to normal System.out) + */ + private String question(String question) throws IOException { + question = question + " " + _PROMPT + " "; + String answer = ""; + // output to normal System.out + System.out.print(question); // intended print, not println (!) + answer = readLine(); + return answer; + } + + /** + * Send a signal through the tunnel, and then wait for the answer from the other side. + * + *
+     *     (2)  [Driver]   receives question  [Tunnel]   sends question   [Console]  (1)
+     *     (3)  [Driver]   sends answer       [Tunnel]   receives answer  [Console]  (4)
+     * 
+ */ + private String readLine() throws IOException { + InputStream inputStream; + String line = ""; + if (_tunnel == null) { + inputStream = System.in; + } else { + inputStream = _tunnel.getAnswerReceiverStream(); + _tunnel.getQuestionSenderStream().write(Tunnel.NEW_LINE.getBytes()); + _tunnel.getQuestionSenderStream().flush(); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + line = reader.readLine(); + return line; + } + +} diff --git a/installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java b/installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/DrivableConsoleTest.java @@ -0,0 +1,37 @@ +package org.python.util.install.driver; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; + +import org.python.util.install.driver.ConsoleDriver; +import org.python.util.install.driver.Tunnel; + +import junit.framework.TestCase; + +public class DrivableConsoleTest extends TestCase { + + private DrivableConsole _console; + private Tunnel _tunnel; + + protected void setUp() throws IOException { + _tunnel = new Tunnel(); + _console = new DrivableConsole(_tunnel); + } + + public void testDrive() throws Exception { + // sequence matters here (have to fork off the driver thread first + ConsoleDriver driver = new ConsoleDriver(_tunnel, getAnswers()); + driver.start(); + _console.handleConsoleIO(); + } + + private Collection getAnswers() { + Collection answers = new ArrayList(); + answers.add("1"); + answers.add("2"); + answers.add("3"); + return answers; + } + +} diff --git a/installer/test/java/org/python/util/install/driver/NormalVerifierTest.java b/installer/test/java/org/python/util/install/driver/NormalVerifierTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/NormalVerifierTest.java @@ -0,0 +1,131 @@ +package org.python.util.install.driver; + +import java.io.File; +import java.io.IOException; + +import junit.framework.TestCase; + +import org.python.util.install.FileHelper; +import org.python.util.install.Installation; +import org.python.util.install.JavaVersionTester; + +public class NormalVerifierTest extends TestCase { + + private static final String DQ = "\""; + + private NormalVerifier _verifier; + + protected void setUp() throws Exception { + super.setUp(); + _verifier = new NormalVerifier(); + // use a directory containing spaces as target directory + File targetDir = createTargetDirectory(); + assertTrue(targetDir.exists()); + assertTrue(targetDir.isDirectory()); + _verifier.setTargetDir(targetDir); + } + + protected void tearDown() throws Exception { + super.tearDown(); + if (_verifier.getTargetDir() != null) { + File autotestFile = new File(_verifier.getTargetDir().getCanonicalPath(), + NormalVerifier.AUTOTEST_PY); + if (autotestFile.exists()) { + assertTrue(autotestFile.delete()); + } + } + } + + // have to install jython first in order to activate this test + public void testVerify() throws Exception {} + + public void testGetSimpleCommand() throws Exception { + String prefix = _verifier.getTargetDir().getCanonicalPath().concat(File.separator); + String expectedCommand = prefix.concat("jython"); + if (Installation.isWindows()) { + expectedCommand = expectedCommand.concat(".bat"); + } + String expectedArgument = prefix.concat("autotest.py"); + String[] command = _verifier.getSimpleCommand(); + assertNotNull(command); + assertEquals(2, command.length); + assertEquals(expectedCommand, command[0]); + assertEquals(expectedArgument, command[1]); + } + + public void testDoShellScriptTests() { + assertTrue(_verifier.doShellScriptTests()); + } + + public void testGetShellScriptTestCommandDir() throws DriverException, IOException { + String expectedDir = _verifier.getTargetDir() + .getCanonicalPath() + .concat(File.separator) + .concat("bin"); + assertEquals(expectedDir, _verifier.getShellScriptTestCommandDir().getCanonicalPath()); + } + + public void testGetShellScriptTestContents() throws Exception { + String contents = _verifier.getShellScriptTestContents(); + // common asserts + assertNotNull(contents); + assertFalse(contents.length() == 0); + assertFalse(contents.indexOf("{0}") > 0); + assertFalse(contents.indexOf("{1}") > 0); + assertFalse(contents.indexOf("{2}") > 0); + assertFalse(contents.indexOf("{3}") > 0); + assertTrue(contents.indexOf("autotest.py") > 0); + String targetDirPath = _verifier.getTargetDir().getCanonicalPath(); + String upScriptPath = _verifier.getSimpleCommand()[1]; + String javaHome = System.getProperty(JavaVersionTester.JAVA_HOME, ""); // change this ++++++ + assertTrue(javaHome.length() > 0); + // platform specific asserts + if (Installation.isWindows()) { + assertTrue(contents.indexOf("set _INSTALL_DIR=") > 0); + assertTrue(contents.indexOf("set _INSTALL_DIR=".concat(targetDirPath)) > 0); + assertTrue(contents.indexOf("set _SCRIPT=") > 0); + assertTrue(contents.indexOf("set _SCRIPT=".concat(upScriptPath)) > 0); + assertTrue(contents.indexOf("set _JAVA_HOME=") > 0); + assertTrue(contents.indexOf("set _JAVA_HOME=".concat(javaHome)) > 0); + } else { + System.out.println(contents); + assertTrue(contents.indexOf("_INSTALL_DIR=") > 0); + assertTrue(contents.indexOf("_INSTALL_DIR=".concat(quote(targetDirPath))) > 0); + assertTrue(contents.indexOf("_SCRIPT=") > 0); + assertTrue(contents.indexOf("_SCRIPT=".concat(quote(upScriptPath))) > 0); + assertTrue(contents.indexOf("_JAVA_HOME=") > 0); + assertTrue(contents.indexOf("_JAVA_HOME=".concat(quote(javaHome))) > 0); + } + } + + public void testGetShellScriptTestCommand() throws Exception { + String prefix = _verifier.getShellScriptTestCommandDir() + .getCanonicalPath() + .concat(File.separator); + String expectedCommand = prefix.concat("jython_test"); + if (Installation.isWindows()) { + expectedCommand = expectedCommand.concat(".bat"); + } + String[] command = _verifier.getShellScriptTestCommand(); + assertNotNull(command); + assertEquals(1, command.length); + String commandFileName = command[0]; + assertEquals(expectedCommand, commandFileName); + File commandFile = new File(commandFileName); + assertTrue(commandFile.exists()); + String contents = FileHelper.readAll(commandFile); + assertNotNull(contents); + assertFalse(contents.length() == 0); + assertEquals(_verifier.getShellScriptTestContents(), contents); + } + + private File createTargetDirectory() throws IOException { + File tmpFile = File.createTempFile("NormalVerifierTest_", "with spaces"); + FileHelper.createTempDirectory(tmpFile); + return tmpFile; + } + + private String quote(String value) { + return DQ.concat(value).concat(DQ); + } +} diff --git a/installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java b/installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java new file mode 100644 --- /dev/null +++ b/installer/test/java/org/python/util/install/driver/StandaloneVerifierTest.java @@ -0,0 +1,72 @@ +package org.python.util.install.driver; + +import java.io.File; + +import org.python.util.install.Installation; +import org.python.util.install.JarInstaller; + +import junit.framework.TestCase; + +public class StandaloneVerifierTest extends TestCase { + + private StandaloneVerifier _verifier; + + protected void setUp() throws Exception { + super.setUp(); + _verifier = new StandaloneVerifier(); + File targetDir = null; + // have to install jython first in order to activate this test + // targetDir = new File("C:/Temp/jython.autoinstall.root_54159_dir/006 + // consoleTest_54165_dir"); + _verifier.setTargetDir(targetDir); + } + + protected void tearDown() throws Exception { + super.tearDown(); + if (_verifier.getTargetDir() != null) { + File autotestFile = new File(_verifier.getTargetDir().getCanonicalPath(), + StandaloneVerifier.AUTOTEST_PY); + if (autotestFile.exists()) { + assertTrue(autotestFile.delete()); + } + } + } + + public void testVerify() throws Exception { + if (_verifier.getTargetDir() != null) { + _verifier.verify(); + } + } + + public void testGetSimpleCommand() throws Exception { + File javaHome = new File(System.getProperty("java.home")); + assertNotNull(javaHome); + assertTrue(javaHome.exists()); + File targetDir = new File(System.getProperty(("user.dir"))); // any existing dir + assertNotNull(targetDir); + assertTrue(targetDir.exists()); + String prefix = targetDir.getCanonicalPath().concat(File.separator); + String expectedCommand = javaHome.getCanonicalPath() + .concat(File.separator) + .concat("bin") + .concat(File.separator) + .concat("java"); + if (Installation.isWindows()) { + expectedCommand = expectedCommand.concat(".exe"); + } + String expectedArgument = prefix.concat("autotest.py"); + _verifier.setTargetDir(targetDir); + String[] command = _verifier.getSimpleCommand(); + assertNotNull(command); + assertEquals(4, command.length); + assertEquals(expectedCommand, command[0]); + assertEquals("-jar", command[1]); + assertEquals(prefix.concat(JarInstaller.JYTHON_JAR), command[2]); + assertEquals(expectedArgument, command[3]); + } + + public void testDoShellScriptTests() { + // we cannot do shell script tests in standalone mode + assertFalse(_verifier.doShellScriptTests()); + } +} diff --git a/lib-python/2.7/BaseHTTPServer.py b/lib-python/2.7/BaseHTTPServer.py --- a/lib-python/2.7/BaseHTTPServer.py +++ b/lib-python/2.7/BaseHTTPServer.py @@ -447,13 +447,13 @@ specified as subsequent arguments (it's just like printf!). - The client host and current date/time are prefixed to - every message. + The client ip address and current date/time are prefixed to every + message. """ sys.stderr.write("%s - - [%s] %s\n" % - (self.address_string(), + (self.client_address[0], self.log_date_time_string(), format%args)) diff --git a/lib-python/2.7/CGIHTTPServer.py b/lib-python/2.7/CGIHTTPServer.py --- a/lib-python/2.7/CGIHTTPServer.py +++ b/lib-python/2.7/CGIHTTPServer.py @@ -84,9 +84,11 @@ path begins with one of the strings in self.cgi_directories (and the next character is a '/' or the end of the string). """ - splitpath = _url_collapse_path_split(self.path) - if splitpath[0] in self.cgi_directories: - self.cgi_info = splitpath + collapsed_path = _url_collapse_path(self.path) + dir_sep = collapsed_path.find('/', 1) + head, tail = collapsed_path[:dir_sep], collapsed_path[dir_sep+1:] + if head in self.cgi_directories: + self.cgi_info = head, tail return True return False @@ -298,51 +300,46 @@ self.log_message("CGI script exited OK") -# TODO(gregory.p.smith): Move this into an appropriate library. -def _url_collapse_path_split(path): +def _url_collapse_path(path): """ Given a URL path, remove extra '/'s and '.' path elements and collapse - any '..' references. + any '..' references and returns a colllapsed path. Implements something akin to RFC-2396 5.2 step 6 to parse relative paths. + The utility of this function is limited to is_cgi method and helps + preventing some security attacks. Returns: A tuple of (head, tail) where tail is everything after the final / and head is everything before it. Head will always start with a '/' and, if it contains anything else, never have a trailing '/'. Raises: IndexError if too many '..' occur within the path. + """ # Similar to os.path.split(os.path.normpath(path)) but specific to URL # path semantics rather than local operating system semantics. - path_parts = [] - for part in path.split('/'): - if part == '.': - path_parts.append('') - else: - path_parts.append(part) - # Filter out blank non trailing parts before consuming the '..'. - path_parts = [part for part in path_parts[:-1] if part] + path_parts[-1:] + path_parts = path.split('/') + head_parts = [] + for part in path_parts[:-1]: + if part == '..': + head_parts.pop() # IndexError if more '..' than prior parts + elif part and part != '.': + head_parts.append( part ) if path_parts: - # Special case for CGI's for PATH_INFO - if path.startswith('/cgi-bin') or path.startswith('/htbin'): - tail_part = [] - while path_parts[-1] not in ('cgi-bin','htbin'): - tail_part.insert(0,path_parts.pop()) - tail_part = "/".join(tail_part) - else: - tail_part = path_parts.pop() + tail_part = path_parts.pop() + if tail_part: + if tail_part == '..': + head_parts.pop() + tail_part = '' + elif tail_part == '.': + tail_part = '' else: tail_part = '' - head_parts = [] - for part in path_parts: - if part == '..': - head_parts.pop() - else: - head_parts.append(part) - if tail_part and tail_part == '..': - head_parts.pop() - tail_part = '' - return ('/' + '/'.join(head_parts), tail_part) + + splitpath = ('/' + '/'.join(head_parts), tail_part) + collapsed_path = "/".join(splitpath) + + return collapsed_path nobody = None diff --git a/lib-python/2.7/Cookie.py b/lib-python/2.7/Cookie.py --- a/lib-python/2.7/Cookie.py +++ b/lib-python/2.7/Cookie.py @@ -390,7 +390,7 @@ from time import gmtime, time now = time() year, month, day, hh, mm, ss, wd, y, z = gmtime(now + future) - return "%s, %02d-%3s-%4d %02d:%02d:%02d GMT" % \ + return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \ (weekdayname[wd], day, monthname[month], year, hh, mm, ss) @@ -539,7 +539,7 @@ r"(?P" # Start of group 'val' r'"(?:[^\\"]|\\.)*"' # Any doublequoted string r"|" # or - r"\w{3},\s[\w\d-]{9,11}\s[\d:]{8}\sGMT" # Special case for "expires" attr + r"\w{3},\s[\s\w\d-]{9,11}\s[\d:]{8}\sGMT" # Special case for "expires" attr r"|" # or ""+ _LegalCharsPatt +"*" # Any word or empty string r")" # End of group 'val' diff --git a/lib-python/2.7/HTMLParser.py b/lib-python/2.7/HTMLParser.py --- a/lib-python/2.7/HTMLParser.py +++ b/lib-python/2.7/HTMLParser.py @@ -22,13 +22,13 @@ starttagopen = re.compile('<[a-zA-Z]') piclose = re.compile('>') commentclose = re.compile(r'--\s*>') -tagfind = re.compile('[a-zA-Z][-.a-zA-Z0-9:_]*') +tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*') # see http://www.w3.org/TR/html5/tokenization.html#tag-open-state # and http://www.w3.org/TR/html5/tokenization.html#tag-name-state tagfind_tolerant = re.compile('[a-zA-Z][^\t\n\r\f />\x00]*') attrfind = re.compile( - r'[\s/]*((?<=[\'"\s/])[^\s/>][^\s/=>]*)(\s*=+\s*' + r'((?<=[\'"\s/])[^\s/>][^\s/=>]*)(\s*=+\s*' r'(\'[^\']*\'|"[^"]*"|(?![\'"])[^>\s]*))?(?:\s|/(?!>))*') locatestarttagend = re.compile(r""" @@ -289,7 +289,7 @@ match = tagfind.match(rawdata, i+1) assert match, 'unexpected call to parse_starttag()' k = match.end() - self.lasttag = tag = rawdata[i+1:k].lower() + self.lasttag = tag = match.group(1).lower() while k < endpos: m = attrfind.match(rawdata, k) diff --git a/lib-python/2.7/SocketServer.py b/lib-python/2.7/SocketServer.py --- a/lib-python/2.7/SocketServer.py +++ b/lib-python/2.7/SocketServer.py @@ -133,6 +133,7 @@ import select import sys import os +import errno try: import threading except ImportError: @@ -147,6 +148,15 @@ "ThreadingUnixStreamServer", "ThreadingUnixDatagramServer"]) +def _eintr_retry(func, *args): + """restart a system call interrupted by EINTR""" + while True: + try: + return func(*args) + except (OSError, select.error) as e: + if e.args[0] != errno.EINTR: + raise + class BaseServer: """Base class for server classes. @@ -222,7 +232,8 @@ # connecting to the socket to wake this up instead of # polling. Polling reduces our responsiveness to a # shutdown request and wastes cpu at all other times. - r, w, e = select.select([self], [], [], poll_interval) + r, w, e = _eintr_retry(select.select, [self], [], [], + poll_interval) if self in r: self._handle_request_noblock() finally: @@ -262,7 +273,7 @@ timeout = self.timeout elif self.timeout is not None: timeout = min(timeout, self.timeout) - fd_sets = select.select([self], [], [], timeout) + fd_sets = _eintr_retry(select.select, [self], [], [], timeout) if not fd_sets[0]: self.handle_timeout() return @@ -690,7 +701,12 @@ def finish(self): if not self.wfile.closed: - self.wfile.flush() + try: + self.wfile.flush() + except socket.error: + # An final socket error may have occurred here, such as + # the local error ECONNABORTED. + pass self.wfile.close() self.rfile.close() diff --git a/lib-python/2.7/StringIO.py b/lib-python/2.7/StringIO.py --- a/lib-python/2.7/StringIO.py +++ b/lib-python/2.7/StringIO.py @@ -158,7 +158,7 @@ newpos = self.len else: newpos = i+1 - if length is not None and length > 0: + if length is not None and length >= 0: if self.pos + length < newpos: newpos = self.pos + length r = self.buf[self.pos:newpos] diff --git a/lib-python/2.7/_LWPCookieJar.py b/lib-python/2.7/_LWPCookieJar.py --- a/lib-python/2.7/_LWPCookieJar.py +++ b/lib-python/2.7/_LWPCookieJar.py @@ -48,7 +48,7 @@ class LWPCookieJar(FileCookieJar): """ - The LWPCookieJar saves a sequence of"Set-Cookie3" lines. + The LWPCookieJar saves a sequence of "Set-Cookie3" lines. "Set-Cookie3" is the format used by the libwww-perl libary, not known to be compatible with any browser, but which is easy to read and doesn't lose information about RFC 2965 cookies. @@ -60,7 +60,7 @@ """ def as_lwp_str(self, ignore_discard=True, ignore_expires=True): - """Return cookies as a string of "\n"-separated "Set-Cookie3" headers. + """Return cookies as a string of "\\n"-separated "Set-Cookie3" headers. ignore_discard and ignore_expires: see docstring for FileCookieJar.save diff --git a/lib-python/2.7/__future__.py b/lib-python/2.7/__future__.py --- a/lib-python/2.7/__future__.py +++ b/lib-python/2.7/__future__.py @@ -112,7 +112,7 @@ CO_FUTURE_DIVISION) absolute_import = _Feature((2, 5, 0, "alpha", 1), - (2, 7, 0, "alpha", 0), + (3, 0, 0, "alpha", 0), CO_FUTURE_ABSOLUTE_IMPORT) with_statement = _Feature((2, 5, 0, "alpha", 1), diff --git a/lib-python/2.7/_osx_support.py b/lib-python/2.7/_osx_support.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/_osx_support.py @@ -0,0 +1,488 @@ +"""Shared OS X support functions.""" + +import os +import re +import sys + +__all__ = [ + 'compiler_fixup', + 'customize_config_vars', + 'customize_compiler', + 'get_platform_osx', +] + +# configuration variables that may contain universal build flags, +# like "-arch" or "-isdkroot", that may need customization for +# the user environment +_UNIVERSAL_CONFIG_VARS = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', 'BASECFLAGS', + 'BLDSHARED', 'LDSHARED', 'CC', 'CXX', + 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS', + 'PY_CORE_CFLAGS') + +# configuration variables that may contain compiler calls +_COMPILER_CONFIG_VARS = ('BLDSHARED', 'LDSHARED', 'CC', 'CXX') + +# prefix added to original configuration variable names +_INITPRE = '_OSX_SUPPORT_INITIAL_' + + +def _find_executable(executable, path=None): + """Tries to find 'executable' in the directories listed in 'path'. + + A string listing directories separated by 'os.pathsep'; defaults to + os.environ['PATH']. Returns the complete filename or None if not found. + """ + if path is None: + path = os.environ['PATH'] + + paths = path.split(os.pathsep) + base, ext = os.path.splitext(executable) + + if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'): + executable = executable + '.exe' + + if not os.path.isfile(executable): + for p in paths: + f = os.path.join(p, executable) + if os.path.isfile(f): + # the file exists, we have a shot at spawn working + return f + return None + else: + return executable + + +def _read_output(commandstring): + """Output from succesful command execution or None""" + # Similar to os.popen(commandstring, "r").read(), + # but without actually using os.popen because that + # function is not usable during python bootstrap. + # tempfile is also not available then. + import contextlib + try: + import tempfile + fp = tempfile.NamedTemporaryFile() + except ImportError: + fp = open("/tmp/_osx_support.%s"%( + os.getpid(),), "w+b") + + with contextlib.closing(fp) as fp: + cmd = "%s 2>/dev/null >'%s'" % (commandstring, fp.name) + return fp.read().decode('utf-8').strip() if not os.system(cmd) else None + + +def _find_build_tool(toolname): + """Find a build tool on current path or using xcrun""" + return (_find_executable(toolname) + or _read_output("/usr/bin/xcrun -find %s" % (toolname,)) + or '' + ) + +_SYSTEM_VERSION = None + +def _get_system_version(): + """Return the OS X system version as a string""" + # Reading this plist is a documented way to get the system + # version (see the documentation for the Gestalt Manager) + # We avoid using platform.mac_ver to avoid possible bootstrap issues during + # the build of Python itself (distutils is used to build standard library + # extensions). + + global _SYSTEM_VERSION + + if _SYSTEM_VERSION is None: + _SYSTEM_VERSION = '' + try: + f = open('/System/Library/CoreServices/SystemVersion.plist') + except IOError: + # We're on a plain darwin box, fall back to the default + # behaviour. + pass + else: + try: + m = re.search(r'ProductUserVisibleVersion\s*' + r'(.*?)', f.read()) + finally: + f.close() + if m is not None: + _SYSTEM_VERSION = '.'.join(m.group(1).split('.')[:2]) + # else: fall back to the default behaviour + + return _SYSTEM_VERSION + +def _remove_original_values(_config_vars): + """Remove original unmodified values for testing""" + # This is needed for higher-level cross-platform tests of get_platform. + for k in list(_config_vars): + if k.startswith(_INITPRE): + del _config_vars[k] + +def _save_modified_value(_config_vars, cv, newvalue): + """Save modified and original unmodified value of configuration var""" + + oldvalue = _config_vars.get(cv, '') + if (oldvalue != newvalue) and (_INITPRE + cv not in _config_vars): + _config_vars[_INITPRE + cv] = oldvalue + _config_vars[cv] = newvalue + +def _supports_universal_builds(): + """Returns True if universal builds are supported on this system""" + # As an approximation, we assume that if we are running on 10.4 or above, + # then we are running with an Xcode environment that supports universal + # builds, in particular -isysroot and -arch arguments to the compiler. This + # is in support of allowing 10.4 universal builds to run on 10.3.x systems. + + osx_version = _get_system_version() + if osx_version: + try: + osx_version = tuple(int(i) for i in osx_version.split('.')) + except ValueError: + osx_version = '' + return bool(osx_version >= (10, 4)) if osx_version else False + + +def _find_appropriate_compiler(_config_vars): + """Find appropriate C compiler for extension module builds""" + + # Issue #13590: + # The OSX location for the compiler varies between OSX + # (or rather Xcode) releases. With older releases (up-to 10.5) + # the compiler is in /usr/bin, with newer releases the compiler + # can only be found inside Xcode.app if the "Command Line Tools" + # are not installed. + # + # Futhermore, the compiler that can be used varies between + # Xcode releases. Upto Xcode 4 it was possible to use 'gcc-4.2' + # as the compiler, after that 'clang' should be used because + # gcc-4.2 is either not present, or a copy of 'llvm-gcc' that + # miscompiles Python. + + # skip checks if the compiler was overriden with a CC env variable + if 'CC' in os.environ: + return _config_vars + + # The CC config var might contain additional arguments. + # Ignore them while searching. + cc = oldcc = _config_vars['CC'].split()[0] + if not _find_executable(cc): + # Compiler is not found on the shell search PATH. + # Now search for clang, first on PATH (if the Command LIne + # Tools have been installed in / or if the user has provided + # another location via CC). If not found, try using xcrun + # to find an uninstalled clang (within a selected Xcode). + + # NOTE: Cannot use subprocess here because of bootstrap + # issues when building Python itself (and os.popen is + # implemented on top of subprocess and is therefore not + # usable as well) + + cc = _find_build_tool('clang') + + elif os.path.basename(cc).startswith('gcc'): + # Compiler is GCC, check if it is LLVM-GCC + data = _read_output("'%s' --version" + % (cc.replace("'", "'\"'\"'"),)) + if 'llvm-gcc' in data: + # Found LLVM-GCC, fall back to clang + cc = _find_build_tool('clang') + + if not cc: + raise SystemError( + "Cannot locate working compiler") + + if cc != oldcc: + # Found a replacement compiler. + # Modify config vars using new compiler, if not already explictly + # overriden by an env variable, preserving additional arguments. + for cv in _COMPILER_CONFIG_VARS: + if cv in _config_vars and cv not in os.environ: + cv_split = _config_vars[cv].split() + cv_split[0] = cc if cv != 'CXX' else cc + '++' + _save_modified_value(_config_vars, cv, ' '.join(cv_split)) + + return _config_vars + + +def _remove_universal_flags(_config_vars): + """Remove all universal build arguments from config vars""" + + for cv in _UNIVERSAL_CONFIG_VARS: + # Do not alter a config var explicitly overriden by env var + if cv in _config_vars and cv not in os.environ: + flags = _config_vars[cv] + flags = re.sub('-arch\s+\w+\s', ' ', flags) + flags = re.sub('-isysroot [^ \t]*', ' ', flags) + _save_modified_value(_config_vars, cv, flags) + + return _config_vars + + +def _remove_unsupported_archs(_config_vars): + """Remove any unsupported archs from config vars""" + # Different Xcode releases support different sets for '-arch' + # flags. In particular, Xcode 4.x no longer supports the + # PPC architectures. + # + # This code automatically removes '-arch ppc' and '-arch ppc64' + # when these are not supported. That makes it possible to + # build extensions on OSX 10.7 and later with the prebuilt + # 32-bit installer on the python.org website. + + # skip checks if the compiler was overriden with a CC env variable + if 'CC' in os.environ: + return _config_vars + + if re.search('-arch\s+ppc', _config_vars['CFLAGS']) is not None: + # NOTE: Cannot use subprocess here because of bootstrap + # issues when building Python itself + status = os.system("'%s' -arch ppc -x c /dev/null 2>/dev/null"%( + _config_vars['CC'].replace("'", "'\"'\"'"),)) + # The Apple compiler drivers return status 255 if no PPC + if (status >> 8) == 255: + # Compiler doesn't support PPC, remove the related + # '-arch' flags if not explicitly overridden by an + # environment variable + for cv in _UNIVERSAL_CONFIG_VARS: + if cv in _config_vars and cv not in os.environ: + flags = _config_vars[cv] + flags = re.sub('-arch\s+ppc\w*\s', ' ', flags) + _save_modified_value(_config_vars, cv, flags) + + return _config_vars + + +def _override_all_archs(_config_vars): + """Allow override of all archs with ARCHFLAGS env var""" + # NOTE: This name was introduced by Apple in OSX 10.5 and + # is used by several scripting languages distributed with + # that OS release. + if 'ARCHFLAGS' in os.environ: + arch = os.environ['ARCHFLAGS'] + for cv in _UNIVERSAL_CONFIG_VARS: + if cv in _config_vars and '-arch' in _config_vars[cv]: + flags = _config_vars[cv] + flags = re.sub('-arch\s+\w+\s', ' ', flags) + flags = flags + ' ' + arch + _save_modified_value(_config_vars, cv, flags) + + return _config_vars + + +def _check_for_unavailable_sdk(_config_vars): + """Remove references to any SDKs not available""" + # If we're on OSX 10.5 or later and the user tries to + # compile an extension using an SDK that is not present + # on the current machine it is better to not use an SDK + # than to fail. This is particularly important with + # the standalong Command Line Tools alternative to a + # full-blown Xcode install since the CLT packages do not + # provide SDKs. If the SDK is not present, it is assumed + # that the header files and dev libs have been installed + # to /usr and /System/Library by either a standalone CLT + # package or the CLT component within Xcode. + cflags = _config_vars.get('CFLAGS', '') + m = re.search(r'-isysroot\s+(\S+)', cflags) + if m is not None: + sdk = m.group(1) + if not os.path.exists(sdk): + for cv in _UNIVERSAL_CONFIG_VARS: + # Do not alter a config var explicitly overriden by env var + if cv in _config_vars and cv not in os.environ: + flags = _config_vars[cv] + flags = re.sub(r'-isysroot\s+\S+(?:\s|$)', ' ', flags) + _save_modified_value(_config_vars, cv, flags) + + return _config_vars + + +def compiler_fixup(compiler_so, cc_args): + """ + This function will strip '-isysroot PATH' and '-arch ARCH' from the + compile flags if the user has specified one them in extra_compile_flags. + + This is needed because '-arch ARCH' adds another architecture to the + build, without a way to remove an architecture. Furthermore GCC will + barf if multiple '-isysroot' arguments are present. + """ + stripArch = stripSysroot = False + + compiler_so = list(compiler_so) + + if not _supports_universal_builds(): + # OSX before 10.4.0, these don't support -arch and -isysroot at + # all. + stripArch = stripSysroot = True + else: + stripArch = '-arch' in cc_args + stripSysroot = '-isysroot' in cc_args + + if stripArch or 'ARCHFLAGS' in os.environ: + while True: + try: + index = compiler_so.index('-arch') + # Strip this argument and the next one: + del compiler_so[index:index+2] + except ValueError: + break + + if 'ARCHFLAGS' in os.environ and not stripArch: + # User specified different -arch flags in the environ, + # see also distutils.sysconfig + compiler_so = compiler_so + os.environ['ARCHFLAGS'].split() + + if stripSysroot: + while True: + try: + index = compiler_so.index('-isysroot') + # Strip this argument and the next one: + del compiler_so[index:index+2] + except ValueError: + break + + # Check if the SDK that is used during compilation actually exists, + # the universal build requires the usage of a universal SDK and not all + # users have that installed by default. + sysroot = None + if '-isysroot' in cc_args: + idx = cc_args.index('-isysroot') + sysroot = cc_args[idx+1] + elif '-isysroot' in compiler_so: + idx = compiler_so.index('-isysroot') + sysroot = compiler_so[idx+1] + + if sysroot and not os.path.isdir(sysroot): + from distutils import log + log.warn("Compiling with an SDK that doesn't seem to exist: %s", + sysroot) + log.warn("Please check your Xcode installation") + + return compiler_so + + +def customize_config_vars(_config_vars): + """Customize Python build configuration variables. + + Called internally from sysconfig with a mutable mapping + containing name/value pairs parsed from the configured + makefile used to build this interpreter. Returns + the mapping updated as needed to reflect the environment + in which the interpreter is running; in the case of + a Python from a binary installer, the installed + environment may be very different from the build + environment, i.e. different OS levels, different + built tools, different available CPU architectures. + + This customization is performed whenever + distutils.sysconfig.get_config_vars() is first + called. It may be used in environments where no + compilers are present, i.e. when installing pure + Python dists. Customization of compiler paths + and detection of unavailable archs is deferred + until the first extention module build is + requested (in distutils.sysconfig.customize_compiler). + + Currently called from distutils.sysconfig + """ + + if not _supports_universal_builds(): + # On Mac OS X before 10.4, check if -arch and -isysroot + # are in CFLAGS or LDFLAGS and remove them if they are. + # This is needed when building extensions on a 10.3 system + # using a universal build of python. + _remove_universal_flags(_config_vars) + + # Allow user to override all archs with ARCHFLAGS env var + _override_all_archs(_config_vars) + + # Remove references to sdks that are not found + _check_for_unavailable_sdk(_config_vars) + + return _config_vars + + +def customize_compiler(_config_vars): + """Customize compiler path and configuration variables. + + This customization is performed when the first + extension module build is requested + in distutils.sysconfig.customize_compiler). + """ + + # Find a compiler to use for extension module builds + _find_appropriate_compiler(_config_vars) + + # Remove ppc arch flags if not supported here + _remove_unsupported_archs(_config_vars) + + # Allow user to override all archs with ARCHFLAGS env var + _override_all_archs(_config_vars) + + return _config_vars + + +def get_platform_osx(_config_vars, osname, release, machine): + """Filter values for get_platform()""" + # called from get_platform() in sysconfig and distutils.util + # + # For our purposes, we'll assume that the system version from + # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set + # to. This makes the compatibility story a bit more sane because the + # machine is going to compile and link as if it were + # MACOSX_DEPLOYMENT_TARGET. + + macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '') + macrelease = _get_system_version() or macver + macver = macver or macrelease + + if macver: + release = macver + osname = "macosx" + + # Use the original CFLAGS value, if available, so that we + # return the same machine type for the platform string. + # Otherwise, distutils may consider this a cross-compiling + # case and disallow installs. + cflags = _config_vars.get(_INITPRE+'CFLAGS', + _config_vars.get('CFLAGS', '')) + if ((macrelease + '.') >= '10.4.' and + '-arch' in cflags.strip()): + # The universal build will build fat binaries, but not on + # systems before 10.4 + + machine = 'fat' + + archs = re.findall('-arch\s+(\S+)', cflags) + archs = tuple(sorted(set(archs))) + + if len(archs) == 1: + machine = archs[0] + elif archs == ('i386', 'ppc'): + machine = 'fat' + elif archs == ('i386', 'x86_64'): + machine = 'intel' + elif archs == ('i386', 'ppc', 'x86_64'): + machine = 'fat3' + elif archs == ('ppc64', 'x86_64'): + machine = 'fat64' + elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'): + machine = 'universal' + else: + raise ValueError( + "Don't know machine value for archs=%r" % (archs,)) + + elif machine == 'i386': + # On OSX the machine type returned by uname is always the + # 32-bit variant, even if the executable architecture is + # the 64-bit variant + if sys.maxint >= 2**32: + machine = 'x86_64' + + elif machine in ('PowerPC', 'Power_Macintosh'): + # Pick a sane name for the PPC architecture. + # See 'i386' case + if sys.maxint >= 2**32: + machine = 'ppc64' + else: + machine = 'ppc' + + return (osname, release, machine) diff --git a/lib-python/2.7/_pyio.py b/lib-python/2.7/_pyio.py --- a/lib-python/2.7/_pyio.py +++ b/lib-python/2.7/_pyio.py @@ -340,8 +340,10 @@ This method has no effect if the file is already closed. """ if not self.__closed: - self.flush() - self.__closed = True + try: + self.flush() + finally: + self.__closed = True def __del__(self): """Destructor. Calls close().""" @@ -883,12 +885,18 @@ return pos def readable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return True def writable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return True def seekable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return True @@ -1546,6 +1554,8 @@ return self._buffer def seekable(self): + if self.closed: + raise ValueError("I/O operation on closed file.") return self._seekable def readable(self): @@ -1560,8 +1570,10 @@ def close(self): if self.buffer is not None and not self.closed: - self.flush() - self.buffer.close() + try: + self.flush() + finally: + self.buffer.close() @property def closed(self): diff --git a/lib-python/2.7/_strptime.py b/lib-python/2.7/_strptime.py --- a/lib-python/2.7/_strptime.py +++ b/lib-python/2.7/_strptime.py @@ -326,7 +326,8 @@ if len(data_string) != found.end(): raise ValueError("unconverted data remains: %s" % data_string[found.end():]) - year = 1900 + + year = None month = day = 1 hour = minute = second = fraction = 0 tz = -1 @@ -425,6 +426,12 @@ else: tz = value break + leap_year_fix = False + if year is None and month == 2 and day == 29: + year = 1904 # 1904 is first leap year of 20th century + leap_year_fix = True + elif year is None: + year = 1900 # If we know the week of the year and what day of that week, we can figure # out the Julian day of the year. if julian == -1 and week_of_year != -1 and weekday != -1: @@ -446,6 +453,12 @@ day = datetime_result.day if weekday == -1: weekday = datetime_date(year, month, day).weekday() + if leap_year_fix: + # the caller didn't supply a year but asked for Feb 29th. We couldn't + # use the default of 1900 for computations. We set it back to ensure + # that February 29th is smaller than March 1st. + year = 1900 + return (time.struct_time((year, month, day, hour, minute, second, weekday, julian, tz)), fraction) diff --git a/lib-python/2.7/aifc.py b/lib-python/2.7/aifc.py --- a/lib-python/2.7/aifc.py +++ b/lib-python/2.7/aifc.py @@ -732,22 +732,28 @@ self._patchheader() def close(self): - self._ensure_header_written(0) - if self._datawritten & 1: - # quick pad to even size - self._file.write(chr(0)) - self._datawritten = self._datawritten + 1 - self._writemarkers() - if self._nframeswritten != self._nframes or \ - self._datalength != self._datawritten or \ - self._marklength: - self._patchheader() - if self._comp: - self._comp.CloseCompressor() - self._comp = None - # Prevent ref cycles - self._convert = None - self._file.close() + if self._file is None: + return + try: + self._ensure_header_written(0) + if self._datawritten & 1: + # quick pad to even size + self._file.write(chr(0)) + self._datawritten = self._datawritten + 1 + self._writemarkers() + if self._nframeswritten != self._nframes or \ + self._datalength != self._datawritten or \ + self._marklength: + self._patchheader() + if self._comp: + self._comp.CloseCompressor() + self._comp = None + finally: + # Prevent ref cycles + self._convert = None + f = self._file + self._file = None + f.close() # # Internal methods. diff --git a/lib-python/2.7/argparse.py b/lib-python/2.7/argparse.py --- a/lib-python/2.7/argparse.py +++ b/lib-python/2.7/argparse.py @@ -740,10 +740,10 @@ - default -- The value to be produced if the option is not specified. - - type -- The type which the command-line arguments should be converted - to, should be one of 'string', 'int', 'float', 'complex' or a - callable object that accepts a single string argument. If None, - 'string' is assumed. + - type -- A callable that accepts a single string argument, and + returns the converted value. The standard Python types str, int, + float, and complex are useful examples of such callables. If None, + str is used. - choices -- A container of values that should be allowed. If not None, after a command-line argument has been converted to the appropriate @@ -1692,9 +1692,12 @@ return args def parse_known_args(self, args=None, namespace=None): - # args default to the system args if args is None: + # args default to the system args args = _sys.argv[1:] + else: + # make sure that args are mutable + args = list(args) # default Namespace built from parser defaults if namespace is None: @@ -1705,10 +1708,7 @@ if action.dest is not SUPPRESS: if not hasattr(namespace, action.dest): if action.default is not SUPPRESS: - default = action.default - if isinstance(action.default, basestring): - default = self._get_value(action, default) - setattr(namespace, action.dest, default) + setattr(namespace, action.dest, action.default) # add any parser defaults that aren't present for dest in self._defaults: @@ -1936,12 +1936,23 @@ if positionals: self.error(_('too few arguments')) - # make sure all required actions were present + # make sure all required actions were present, and convert defaults. for action in self._actions: - if action.required: - if action not in seen_actions: + if action not in seen_actions: + if action.required: name = _get_action_name(action) self.error(_('argument %s is required') % name) + else: + # Convert action default now instead of doing it before + # parsing arguments to avoid calling convert functions + # twice (which may fail) if the argument was given, but + # only if it was defined already in the namespace + if (action.default is not None and + isinstance(action.default, basestring) and + hasattr(namespace, action.dest) and + action.default is getattr(namespace, action.dest)): + setattr(namespace, action.dest, + self._get_value(action, action.default)) # make sure all required groups had one option present for group in self._mutually_exclusive_groups: @@ -1967,7 +1978,7 @@ for arg_string in arg_strings: # for regular arguments, just add them back into the list - if arg_string[0] not in self.fromfile_prefix_chars: + if not arg_string or arg_string[0] not in self.fromfile_prefix_chars: new_arg_strings.append(arg_string) # replace arguments referencing files with the file content @@ -2174,9 +2185,12 @@ # Value conversion methods # ======================== def _get_values(self, action, arg_strings): - # for everything but PARSER args, strip out '--' + # for everything but PARSER, REMAINDER args, strip out first '--' if action.nargs not in [PARSER, REMAINDER]: - arg_strings = [s for s in arg_strings if s != '--'] + try: + arg_strings.remove('--') + except ValueError: + pass # optional argument produces a default when not present if not arg_strings and action.nargs == OPTIONAL: diff --git a/lib-python/2.7/asyncore.py b/lib-python/2.7/asyncore.py --- a/lib-python/2.7/asyncore.py +++ b/lib-python/2.7/asyncore.py @@ -225,6 +225,7 @@ debug = False connected = False accepting = False + connecting = False closing = False addr = None ignore_log_types = frozenset(['warning']) @@ -248,7 +249,7 @@ try: self.addr = sock.getpeername() except socket.error, err: - if err.args[0] == ENOTCONN: + if err.args[0] in (ENOTCONN, EINVAL): # To handle the case where we got an unconnected # socket. self.connected = False @@ -342,9 +343,11 @@ def connect(self, address): self.connected = False + self.connecting = True err = self.socket.connect_ex(address) if err in (EINPROGRESS, EALREADY, EWOULDBLOCK) \ or err == EINVAL and os.name in ('nt', 'ce'): + self.addr = address return if err in (0, EISCONN): self.addr = address @@ -390,7 +393,7 @@ else: return data except socket.error, why: - # winsock sometimes throws ENOTCONN + # winsock sometimes raises ENOTCONN if why.args[0] in _DISCONNECTED: self.handle_close() return '' @@ -400,6 +403,7 @@ def close(self): self.connected = False self.accepting = False + self.connecting = False self.del_channel() try: self.socket.close() @@ -438,7 +442,8 @@ # sockets that are connected self.handle_accept() elif not self.connected: - self.handle_connect_event() + if self.connecting: + self.handle_connect_event() self.handle_read() else: self.handle_read() @@ -449,6 +454,7 @@ raise socket.error(err, _strerror(err)) self.handle_connect() self.connected = True + self.connecting = False def handle_write_event(self): if self.accepting: @@ -457,12 +463,8 @@ return if not self.connected: - #check for errors - err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) - if err != 0: - raise socket.error(err, _strerror(err)) - - self.handle_connect_event() + if self.connecting: + self.handle_connect_event() self.handle_write() def handle_expt_event(self): diff --git a/lib-python/2.7/bdb.py b/lib-python/2.7/bdb.py --- a/lib-python/2.7/bdb.py +++ b/lib-python/2.7/bdb.py @@ -24,6 +24,7 @@ self.skip = set(skip) if skip else None self.breaks = {} self.fncache = {} + self.frame_returning = None def canonic(self, filename): if filename == "<" + filename[1:-1] + ">": @@ -82,7 +83,11 @@ def dispatch_return(self, frame, arg): if self.stop_here(frame) or frame == self.returnframe: - self.user_return(frame, arg) + try: + self.frame_returning = frame + self.user_return(frame, arg) + finally: + self.frame_returning = None if self.quitting: raise BdbQuit return self.trace_dispatch @@ -186,6 +191,14 @@ def set_step(self): """Stop after one line of code.""" + # Issue #13183: pdb skips frames after hitting a breakpoint and running + # step commands. + # Restore the trace function in the caller (that may not have been set + # for performance reasons) when returning from the current frame. + if self.frame_returning: + caller_frame = self.frame_returning.f_back + if caller_frame and not caller_frame.f_trace: + caller_frame.f_trace = self.trace_dispatch self._set_stopinfo(None, None) def set_next(self, frame): diff --git a/lib-python/2.7/calendar.py b/lib-python/2.7/calendar.py --- a/lib-python/2.7/calendar.py +++ b/lib-python/2.7/calendar.py @@ -161,7 +161,11 @@ oneday = datetime.timedelta(days=1) while True: yield date - date += oneday + try: + date += oneday + except OverflowError: + # Adding one day could fail after datetime.MAXYEAR + break if date.month != month and date.weekday() == self.firstweekday: break @@ -488,6 +492,7 @@ def __enter__(self): self.oldlocale = _locale.getlocale(_locale.LC_TIME) _locale.setlocale(_locale.LC_TIME, self.locale) + return _locale.getlocale(_locale.LC_TIME)[1] def __exit__(self, *args): _locale.setlocale(_locale.LC_TIME, self.oldlocale) diff --git a/lib-python/2.7/cgi.py b/lib-python/2.7/cgi.py --- a/lib-python/2.7/cgi.py +++ b/lib-python/2.7/cgi.py @@ -37,7 +37,6 @@ from operator import attrgetter import sys import os -import urllib import UserDict import urlparse diff --git a/lib-python/2.7/cgitb.py b/lib-python/2.7/cgitb.py --- a/lib-python/2.7/cgitb.py +++ b/lib-python/2.7/cgitb.py @@ -295,14 +295,19 @@ if self.logdir is not None: suffix = ['.txt', '.html'][self.format=="html"] (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir) + try: file = os.fdopen(fd, 'w') file.write(doc) file.close() - msg = '

%s contains the description of this error.' % path + msg = '%s contains the description of this error.' % path except: - msg = '

Tried to save traceback to %s, but failed.' % path - self.file.write(msg + '\n') + msg = 'Tried to save traceback to %s, but failed.' % path + + if self.format == 'html': + self.file.write('

%s

\n' % msg) + else: + self.file.write(msg + '\n') try: self.file.flush() except: pass diff --git a/lib-python/2.7/cmd.py b/lib-python/2.7/cmd.py --- a/lib-python/2.7/cmd.py +++ b/lib-python/2.7/cmd.py @@ -294,6 +294,7 @@ return list(commands | topics) def do_help(self, arg): + 'List available commands with "help" or detailed help with "help cmd".' if arg: # XXX check arg syntax try: diff --git a/lib-python/2.7/collections.py b/lib-python/2.7/collections.py --- a/lib-python/2.7/collections.py +++ b/lib-python/2.7/collections.py @@ -6,11 +6,12 @@ __all__ += _abcoll.__all__ from _collections import deque, defaultdict -from operator import itemgetter as _itemgetter +from operator import itemgetter as _itemgetter, eq as _eq from keyword import iskeyword as _iskeyword import sys as _sys import heapq as _heapq from itertools import repeat as _repeat, chain as _chain, starmap as _starmap +from itertools import imap as _imap try: from thread import get_ident as _get_ident @@ -50,49 +51,45 @@ self.__map = {} self.__update(*args, **kwds) - def __setitem__(self, key, value, PREV=0, NEXT=1, dict_setitem=dict.__setitem__): + def __setitem__(self, key, value, dict_setitem=dict.__setitem__): 'od.__setitem__(i, y) <==> od[i]=y' # Setting a new item creates a new link at the end of the linked list, # and the inherited dictionary is updated with the new key/value pair. if key not in self: root = self.__root - last = root[PREV] - last[NEXT] = root[PREV] = self.__map[key] = [last, root, key] - dict_setitem(self, key, value) + last = root[0] + last[1] = root[0] = self.__map[key] = [last, root, key] + return dict_setitem(self, key, value) - def __delitem__(self, key, PREV=0, NEXT=1, dict_delitem=dict.__delitem__): + def __delitem__(self, key, dict_delitem=dict.__delitem__): 'od.__delitem__(y) <==> del od[y]' # Deleting an existing item uses self.__map to find the link which gets # removed by updating the links in the predecessor and successor nodes. dict_delitem(self, key) link_prev, link_next, key = self.__map.pop(key) - link_prev[NEXT] = link_next - link_next[PREV] = link_prev + link_prev[1] = link_next # update link_prev[NEXT] + link_next[0] = link_prev # update link_next[PREV] def __iter__(self): 'od.__iter__() <==> iter(od)' # Traverse the linked list in order. - NEXT, KEY = 1, 2 root = self.__root - curr = root[NEXT] + curr = root[1] # start at the first node while curr is not root: - yield curr[KEY] - curr = curr[NEXT] + yield curr[2] # yield the curr[KEY] + curr = curr[1] # move to next node def __reversed__(self): 'od.__reversed__() <==> reversed(od)' # Traverse the linked list in reverse order. - PREV, KEY = 0, 2 root = self.__root - curr = root[PREV] + curr = root[0] # start at the last node while curr is not root: - yield curr[KEY] - curr = curr[PREV] + yield curr[2] # yield the curr[KEY] + curr = curr[0] # move to previous node def clear(self): 'od.clear() -> None. Remove all items from od.' - for node in self.__map.itervalues(): - del node[:] root = self.__root root[:] = [root, root, None] self.__map.clear() @@ -208,7 +205,7 @@ ''' if isinstance(other, OrderedDict): - return len(self)==len(other) and self.items() == other.items() + return dict.__eq__(self, other) and all(_imap(_eq, self, other)) return dict.__eq__(self, other) def __ne__(self, other): @@ -234,10 +231,60 @@ ### namedtuple ################################################################################ +_class_template = '''\ +class {typename}(tuple): + '{typename}({arg_list})' + + __slots__ = () + + _fields = {field_names!r} + + def __new__(_cls, {arg_list}): + 'Create new instance of {typename}({arg_list})' + return _tuple.__new__(_cls, ({arg_list})) + + @classmethod + def _make(cls, iterable, new=tuple.__new__, len=len): + 'Make a new {typename} object from a sequence or iterable' + result = new(cls, iterable) + if len(result) != {num_fields:d}: + raise TypeError('Expected {num_fields:d} arguments, got %d' % len(result)) + return result + + def __repr__(self): + 'Return a nicely formatted representation string' + return '{typename}({repr_fmt})' % self + + def _asdict(self): + 'Return a new OrderedDict which maps field names to their values' + return OrderedDict(zip(self._fields, self)) + + __dict__ = property(_asdict) + + def _replace(_self, **kwds): + 'Return a new {typename} object replacing specified fields with new values' + result = _self._make(map(kwds.pop, {field_names!r}, _self)) + if kwds: + raise ValueError('Got unexpected field names: %r' % kwds.keys()) + return result + + def __getnewargs__(self): + 'Return self as a plain tuple. Used by copy and pickle.' + return tuple(self) + +{field_defs} +''' + +_repr_template = '{name}=%r' + +_field_template = '''\ + {name} = _property(_itemgetter({index:d}), doc='Alias for field number {index:d}') +''' + def namedtuple(typename, field_names, verbose=False, rename=False): """Returns a new subclass of tuple with named fields. - >>> Point = namedtuple('Point', 'x y') + >>> Point = namedtuple('Point', ['x', 'y']) >>> Point.__doc__ # docstring for the new class 'Point(x, y)' >>> p = Point(11, y=22) # instantiate with positional args or keywords @@ -258,83 +305,63 @@ """ - # Parse and validate the field names. Validation serves two purposes, - # generating informative error messages and preventing template injection attacks. + # Validate the field names. At the user's option, either generate an error + # message or automatically replace the field name with a valid name. if isinstance(field_names, basestring): - field_names = field_names.replace(',', ' ').split() # names separated by whitespace and/or commas - field_names = tuple(map(str, field_names)) + field_names = field_names.replace(',', ' ').split() + field_names = map(str, field_names) if rename: - names = list(field_names) seen = set() - for i, name in enumerate(names): - if (not all(c.isalnum() or c=='_' for c in name) or _iskeyword(name) - or not name or name[0].isdigit() or name.startswith('_') + for index, name in enumerate(field_names): + if (not all(c.isalnum() or c=='_' for c in name) + or _iskeyword(name) + or not name + or name[0].isdigit() + or name.startswith('_') or name in seen): - names[i] = '_%d' % i + field_names[index] = '_%d' % index seen.add(name) - field_names = tuple(names) - for name in (typename,) + field_names: + for name in [typename] + field_names: if not all(c.isalnum() or c=='_' for c in name): - raise ValueError('Type names and field names can only contain alphanumeric characters and underscores: %r' % name) + raise ValueError('Type names and field names can only contain ' + 'alphanumeric characters and underscores: %r' % name) if _iskeyword(name): - raise ValueError('Type names and field names cannot be a keyword: %r' % name) + raise ValueError('Type names and field names cannot be a ' + 'keyword: %r' % name) if name[0].isdigit(): - raise ValueError('Type names and field names cannot start with a number: %r' % name) - seen_names = set() + raise ValueError('Type names and field names cannot start with ' + 'a number: %r' % name) + seen = set() for name in field_names: if name.startswith('_') and not rename: - raise ValueError('Field names cannot start with an underscore: %r' % name) - if name in seen_names: + raise ValueError('Field names cannot start with an underscore: ' + '%r' % name) + if name in seen: raise ValueError('Encountered duplicate field name: %r' % name) - seen_names.add(name) + seen.add(name) - # Create and fill-in the class template - numfields = len(field_names) - argtxt = repr(field_names).replace("'", "")[1:-1] # tuple repr without parens or quotes - reprtxt = ', '.join('%s=%%r' % name for name in field_names) - template = '''class %(typename)s(tuple): - '%(typename)s(%(argtxt)s)' \n - __slots__ = () \n - _fields = %(field_names)r \n - def __new__(_cls, %(argtxt)s): - 'Create new instance of %(typename)s(%(argtxt)s)' - return _tuple.__new__(_cls, (%(argtxt)s)) \n - @classmethod - def _make(cls, iterable, new=tuple.__new__, len=len): - 'Make a new %(typename)s object from a sequence or iterable' - result = new(cls, iterable) - if len(result) != %(numfields)d: - raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result)) - return result \n - def __repr__(self): - 'Return a nicely formatted representation string' - return '%(typename)s(%(reprtxt)s)' %% self \n - def _asdict(self): - 'Return a new OrderedDict which maps field names to their values' - return OrderedDict(zip(self._fields, self)) \n - __dict__ = property(_asdict) \n - def _replace(_self, **kwds): - 'Return a new %(typename)s object replacing specified fields with new values' - result = _self._make(map(kwds.pop, %(field_names)r, _self)) - if kwds: - raise ValueError('Got unexpected field names: %%r' %% kwds.keys()) - return result \n - def __getnewargs__(self): - 'Return self as a plain tuple. Used by copy and pickle.' - return tuple(self) \n\n''' % locals() - for i, name in enumerate(field_names): - template += " %s = _property(_itemgetter(%d), doc='Alias for field number %d')\n" % (name, i, i) + # Fill-in the class template + class_definition = _class_template.format( + typename = typename, + field_names = tuple(field_names), + num_fields = len(field_names), + arg_list = repr(tuple(field_names)).replace("'", "")[1:-1], + repr_fmt = ', '.join(_repr_template.format(name=name) + for name in field_names), + field_defs = '\n'.join(_field_template.format(index=index, name=name) + for index, name in enumerate(field_names)) + ) if verbose: - print template + print class_definition - # Execute the template string in a temporary namespace and - # support tracing utilities by setting a value for frame.f_globals['__name__'] + # Execute the template string in a temporary namespace and support + # tracing utilities by setting a value for frame.f_globals['__name__'] namespace = dict(_itemgetter=_itemgetter, __name__='namedtuple_%s' % typename, OrderedDict=OrderedDict, _property=property, _tuple=tuple) try: - exec template in namespace - except SyntaxError, e: - raise SyntaxError(e.message + ':\n' + template) + exec class_definition in namespace + except SyntaxError as e: + raise SyntaxError(e.message + ':\n' + class_definition) result = namespace[typename] # For pickling to work, the __module__ variable needs to be set to the frame diff --git a/lib-python/2.7/compiler/consts.py b/lib-python/2.7/compiler/consts.py --- a/lib-python/2.7/compiler/consts.py +++ b/lib-python/2.7/compiler/consts.py @@ -5,7 +5,7 @@ SC_LOCAL = 1 SC_GLOBAL_IMPLICIT = 2 -SC_GLOBAL_EXPLICT = 3 +SC_GLOBAL_EXPLICIT = 3 SC_FREE = 4 SC_CELL = 5 SC_UNKNOWN = 6 diff --git a/lib-python/2.7/compiler/pycodegen.py b/lib-python/2.7/compiler/pycodegen.py --- a/lib-python/2.7/compiler/pycodegen.py +++ b/lib-python/2.7/compiler/pycodegen.py @@ -7,7 +7,7 @@ from compiler import ast, parse, walk, syntax from compiler import pyassem, misc, future, symbols -from compiler.consts import SC_LOCAL, SC_GLOBAL_IMPLICIT, SC_GLOBAL_EXPLICT, \ +from compiler.consts import SC_LOCAL, SC_GLOBAL_IMPLICIT, SC_GLOBAL_EXPLICIT, \ SC_FREE, SC_CELL from compiler.consts import (CO_VARARGS, CO_VARKEYWORDS, CO_NEWLOCALS, CO_NESTED, CO_GENERATOR, CO_FUTURE_DIVISION, @@ -283,7 +283,7 @@ self.emit(prefix + '_NAME', name) else: self.emit(prefix + '_FAST', name) - elif scope == SC_GLOBAL_EXPLICT: + elif scope == SC_GLOBAL_EXPLICIT: self.emit(prefix + '_GLOBAL', name) elif scope == SC_GLOBAL_IMPLICIT: if not self.optimized: diff --git a/lib-python/2.7/compiler/symbols.py b/lib-python/2.7/compiler/symbols.py --- a/lib-python/2.7/compiler/symbols.py +++ b/lib-python/2.7/compiler/symbols.py @@ -1,7 +1,7 @@ """Module symbol-table generator""" from compiler import ast -from compiler.consts import SC_LOCAL, SC_GLOBAL_IMPLICIT, SC_GLOBAL_EXPLICT, \ +from compiler.consts import SC_LOCAL, SC_GLOBAL_IMPLICIT, SC_GLOBAL_EXPLICIT, \ SC_FREE, SC_CELL, SC_UNKNOWN from compiler.misc import mangle import types @@ -90,7 +90,7 @@ The scope of a name could be LOCAL, GLOBAL, FREE, or CELL. """ if name in self.globals: - return SC_GLOBAL_EXPLICT + return SC_GLOBAL_EXPLICIT if name in self.cells: return SC_CELL if name in self.defs: diff --git a/lib-python/2.7/ctypes/test/test_bitfields.py b/lib-python/2.7/ctypes/test/test_bitfields.py --- a/lib-python/2.7/ctypes/test/test_bitfields.py +++ b/lib-python/2.7/ctypes/test/test_bitfields.py @@ -240,5 +240,25 @@ _anonymous_ = ["_"] _fields_ = [("_", X)] + @unittest.skipUnless(hasattr(ctypes, "c_uint32"), "c_int32 is required") + def test_uint32(self): + class X(Structure): + _fields_ = [("a", c_uint32, 32)] + x = X() + x.a = 10 + self.assertEqual(x.a, 10) + x.a = 0xFDCBA987 + self.assertEqual(x.a, 0xFDCBA987) + + @unittest.skipUnless(hasattr(ctypes, "c_uint64"), "c_int64 is required") + def test_uint64(self): + class X(Structure): + _fields_ = [("a", c_uint64, 64)] + x = X() + x.a = 10 + self.assertEqual(x.a, 10) + x.a = 0xFEDCBA9876543211 + self.assertEqual(x.a, 0xFEDCBA9876543211) + if __name__ == "__main__": unittest.main() diff --git a/lib-python/2.7/ctypes/test/test_numbers.py b/lib-python/2.7/ctypes/test/test_numbers.py --- a/lib-python/2.7/ctypes/test/test_numbers.py +++ b/lib-python/2.7/ctypes/test/test_numbers.py @@ -216,6 +216,16 @@ # probably be changed: self.assertRaises(TypeError, c_int, c_long(42)) + def test_float_overflow(self): + import sys + big_int = int(sys.float_info.max) * 2 + for t in float_types + [c_longdouble]: + self.assertRaises(OverflowError, t, big_int) + if (hasattr(t, "__ctype_be__")): + self.assertRaises(OverflowError, t.__ctype_be__, big_int) + if (hasattr(t, "__ctype_le__")): + self.assertRaises(OverflowError, t.__ctype_le__, big_int) + ## def test_perf(self): ## check_perf() diff --git a/lib-python/2.7/ctypes/test/test_returnfuncptrs.py b/lib-python/2.7/ctypes/test/test_returnfuncptrs.py --- a/lib-python/2.7/ctypes/test/test_returnfuncptrs.py +++ b/lib-python/2.7/ctypes/test/test_returnfuncptrs.py @@ -1,5 +1,6 @@ import unittest from ctypes import * +import os import _ctypes_test @@ -31,5 +32,34 @@ self.assertRaises(ArgumentError, strchr, "abcdef", 3) self.assertRaises(TypeError, strchr, "abcdef") + def test_from_dll(self): + dll = CDLL(_ctypes_test.__file__) + # _CFuncPtr instances are now callable with a tuple argument + # which denotes a function name and a dll: + strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)(("my_strchr", dll)) + self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") + self.assertEqual(strchr(b"abcdef", b"x"), None) + self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) + self.assertRaises(TypeError, strchr, b"abcdef") + + # Issue 6083: Reference counting bug + def test_from_dll_refcount(self): + class BadSequence(tuple): + def __getitem__(self, key): + if key == 0: + return "my_strchr" + if key == 1: + return CDLL(_ctypes_test.__file__) + raise IndexError + + # _CFuncPtr instances are now callable with a tuple argument + # which denotes a function name and a dll: + strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)( + BadSequence(("my_strchr", CDLL(_ctypes_test.__file__)))) + self.assertTrue(strchr(b"abcdef", b"b"), "bcdef") + self.assertEqual(strchr(b"abcdef", b"x"), None) + self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0) + self.assertRaises(TypeError, strchr, b"abcdef") + if __name__ == "__main__": unittest.main() diff --git a/lib-python/2.7/ctypes/test/test_structures.py b/lib-python/2.7/ctypes/test/test_structures.py --- a/lib-python/2.7/ctypes/test/test_structures.py +++ b/lib-python/2.7/ctypes/test/test_structures.py @@ -1,6 +1,7 @@ import unittest from ctypes import * from struct import calcsize +import _testcapi class SubclassesTest(unittest.TestCase): def test_subclass(self): @@ -199,6 +200,14 @@ "_pack_": -1} self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + # Issue 15989 + d = {"_fields_": [("a", c_byte)], + "_pack_": _testcapi.INT_MAX + 1} + self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + d = {"_fields_": [("a", c_byte)], + "_pack_": _testcapi.UINT_MAX + 2} + self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + def test_initializers(self): class Person(Structure): _fields_ = [("name", c_char*6), diff --git a/lib-python/2.7/ctypes/test/test_win32.py b/lib-python/2.7/ctypes/test/test_win32.py --- a/lib-python/2.7/ctypes/test/test_win32.py +++ b/lib-python/2.7/ctypes/test/test_win32.py @@ -3,6 +3,7 @@ from ctypes import * from ctypes.test import is_resource_enabled import unittest, sys +from test import test_support as support import _ctypes_test @@ -60,7 +61,9 @@ def test_COMError(self): from _ctypes import COMError - self.assertEqual(COMError.__doc__, "Raised when a COM method call failed.") + if support.HAVE_DOCSTRINGS: + self.assertEqual(COMError.__doc__, + "Raised when a COM method call failed.") ex = COMError(-1, "text", ("details",)) self.assertEqual(ex.hresult, -1) diff --git a/lib-python/2.7/ctypes/util.py b/lib-python/2.7/ctypes/util.py --- a/lib-python/2.7/ctypes/util.py +++ b/lib-python/2.7/ctypes/util.py @@ -180,6 +180,35 @@ res.sort(cmp= lambda x,y: cmp(_num_version(x), _num_version(y))) return res[-1] + elif sys.platform == "sunos5": + + def _findLib_crle(name, is64): + if not os.path.exists('/usr/bin/crle'): + return None + + if is64: + cmd = 'env LC_ALL=C /usr/bin/crle -64 2>/dev/null' + else: + cmd = 'env LC_ALL=C /usr/bin/crle 2>/dev/null' + + for line in os.popen(cmd).readlines(): + line = line.strip() + if line.startswith('Default Library Path (ELF):'): + paths = line.split()[4] + + if not paths: + return None + + for dir in paths.split(":"): + libfile = os.path.join(dir, "lib%s.so" % name) + if os.path.exists(libfile): + return libfile + + return None + + def find_library(name, is64 = False): + return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name)) + else: def _findSoname_ldconfig(name): diff --git a/lib-python/2.7/curses/__init__.py b/lib-python/2.7/curses/__init__.py --- a/lib-python/2.7/curses/__init__.py +++ b/lib-python/2.7/curses/__init__.py @@ -5,7 +5,7 @@ import curses from curses import textpad - curses.initwin() + curses.initscr() ... """ diff --git a/lib-python/2.7/decimal.py b/lib-python/2.7/decimal.py --- a/lib-python/2.7/decimal.py +++ b/lib-python/2.7/decimal.py @@ -1581,7 +1581,13 @@ def __float__(self): """Float representation.""" - return float(str(self)) + if self._isnan(): + if self.is_snan(): + raise ValueError("Cannot convert signaling NaN to float") + s = "-nan" if self._sign else "nan" + else: + s = str(self) + return float(s) def __int__(self): """Converts self to an int, truncating if necessary.""" diff --git a/lib-python/2.7/distutils/__init__.py b/lib-python/2.7/distutils/__init__.py --- a/lib-python/2.7/distutils/__init__.py +++ b/lib-python/2.7/distutils/__init__.py @@ -15,5 +15,5 @@ # Updated automatically by the Python release process. # #--start constants-- -__version__ = "2.7.3rc2" +__version__ = "2.7.3" #--end constants-- diff --git a/lib-python/2.7/distutils/ccompiler.py b/lib-python/2.7/distutils/ccompiler.py --- a/lib-python/2.7/distutils/ccompiler.py +++ b/lib-python/2.7/distutils/ccompiler.py @@ -17,6 +17,8 @@ from distutils.dep_util import newer_group from distutils.util import split_quoted, execute from distutils import log +# following import is for backward compatibility +from distutils.sysconfig import customize_compiler class CCompiler: """Abstract base class to define the interface that must be implemented diff --git a/lib-python/2.7/distutils/command/check.py b/lib-python/2.7/distutils/command/check.py --- a/lib-python/2.7/distutils/command/check.py +++ b/lib-python/2.7/distutils/command/check.py @@ -26,6 +26,9 @@ def system_message(self, level, message, *children, **kwargs): self.messages.append((level, message, children, kwargs)) + return nodes.system_message(message, level=level, + type=self.levels[level], + *children, **kwargs) HAS_DOCUTILS = True except ImportError: diff --git a/lib-python/2.7/distutils/config.py b/lib-python/2.7/distutils/config.py --- a/lib-python/2.7/distutils/config.py +++ b/lib-python/2.7/distutils/config.py @@ -42,16 +42,11 @@ def _store_pypirc(self, username, password): """Creates a default .pypirc file.""" rc = self._get_rc_file() - f = open(rc, 'w') + f = os.fdopen(os.open(rc, os.O_CREAT | os.O_WRONLY, 0600), 'w') try: f.write(DEFAULT_PYPIRC % (username, password)) finally: f.close() - try: - os.chmod(rc, 0600) - except OSError: - # should do something better here - pass def _read_pypirc(self): """Reads the .pypirc file.""" diff --git a/lib-python/2.7/distutils/dir_util.py b/lib-python/2.7/distutils/dir_util.py --- a/lib-python/2.7/distutils/dir_util.py +++ b/lib-python/2.7/distutils/dir_util.py @@ -144,6 +144,10 @@ src_name = os.path.join(src, n) dst_name = os.path.join(dst, n) + if n.startswith('.nfs'): + # skip NFS rename files + continue + if preserve_symlinks and os.path.islink(src_name): link_dest = os.readlink(src_name) if verbose >= 1: diff --git a/lib-python/2.7/distutils/sysconfig.py b/lib-python/2.7/distutils/sysconfig.py --- a/lib-python/2.7/distutils/sysconfig.py +++ b/lib-python/2.7/distutils/sysconfig.py @@ -37,6 +37,11 @@ project_base = os.path.abspath(os.path.join(project_base, os.path.pardir, os.path.pardir)) +# set for cross builds +if "_PYTHON_PROJECT_BASE" in os.environ: + # this is the build directory, at least for posix + project_base = os.path.normpath(os.environ["_PYTHON_PROJECT_BASE"]) + # python_build: (Boolean) if true, we're either building Python or # building an extension with an un-installed Python, so we use # different (hard-wired) directories. @@ -141,7 +146,7 @@ "I don't know where Python installs its library " "on platform '%s'" % os.name) -_USE_CLANG = None + def customize_compiler(compiler): """Do any platform-specific customization of a CCompiler instance. @@ -150,6 +155,21 @@ varies across Unices and is stored in Python's Makefile. """ if compiler.compiler_type == "unix": + if sys.platform == "darwin": + # Perform first-time customization of compiler-related + # config vars on OS X now that we know we need a compiler. + # This is primarily to support Pythons from binary + # installers. The kind and paths to build tools on + # the user system may vary significantly from the system + # that Python itself was built on. Also the user OS + # version and build tools may not support the same set + # of CPU architectures for universal builds. + global _config_vars + if not _config_vars.get('CUSTOMIZED_OSX_COMPILER', ''): + import _osx_support + _osx_support.customize_compiler(_config_vars) + _config_vars['CUSTOMIZED_OSX_COMPILER'] = 'True' + (cc, cxx, opt, cflags, ccshared, ldshared, so_ext, ar, ar_flags) = \ get_config_vars('CC', 'CXX', 'OPT', 'CFLAGS', 'CCSHARED', 'LDSHARED', 'SO', 'AR', @@ -157,36 +177,7 @@ newcc = None if 'CC' in os.environ: - newcc = os.environ['CC'] - elif sys.platform == 'darwin' and cc == 'gcc-4.2': - # Issue #13590: - # Since Apple removed gcc-4.2 in Xcode 4.2, we can no - # longer assume it is available for extension module builds. - # If Python was built with gcc-4.2, check first to see if - # it is available on this system; if not, try to use clang - # instead unless the caller explicitly set CC. - global _USE_CLANG - if _USE_CLANG is None: - from distutils import log - from subprocess import Popen, PIPE - p = Popen("! type gcc-4.2 && type clang && exit 2", - shell=True, stdout=PIPE, stderr=PIPE) - p.wait() - if p.returncode == 2: - _USE_CLANG = True - log.warn("gcc-4.2 not found, using clang instead") - else: - _USE_CLANG = False - if _USE_CLANG: - newcc = 'clang' - if newcc: - # On OS X, if CC is overridden, use that as the default - # command for LDSHARED as well - if (sys.platform == 'darwin' - and 'LDSHARED' not in os.environ - and ldshared.startswith(cc)): - ldshared = newcc + ldshared[len(cc):] - cc = newcc + cc = os.environ['CC'] if 'CXX' in os.environ: cxx = os.environ['CXX'] if 'LDSHARED' in os.environ: @@ -244,7 +235,7 @@ def get_makefile_filename(): """Return full pathname of installed Makefile from the Python build.""" if python_build: - return os.path.join(os.path.dirname(sys.executable), "Makefile") + return os.path.join(project_base, "Makefile") lib_dir = get_python_lib(plat_specific=1, standard_lib=1) return os.path.join(lib_dir, "config", "Makefile") @@ -518,66 +509,11 @@ _config_vars['prefix'] = PREFIX _config_vars['exec_prefix'] = EXEC_PREFIX + # OS X platforms require special customization to handle + # multi-architecture, multi-os-version installers if sys.platform == 'darwin': - kernel_version = os.uname()[2] # Kernel version (8.4.3) - major_version = int(kernel_version.split('.')[0]) - - if major_version < 8: - # On Mac OS X before 10.4, check if -arch and -isysroot - # are in CFLAGS or LDFLAGS and remove them if they are. - # This is needed when building extensions on a 10.3 system - # using a universal build of python. - for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - flags = _config_vars[key] - flags = re.sub('-arch\s+\w+\s', ' ', flags) - flags = re.sub('-isysroot [^ \t]*', ' ', flags) - _config_vars[key] = flags - - else: - - # Allow the user to override the architecture flags using - # an environment variable. - # NOTE: This name was introduced by Apple in OSX 10.5 and - # is used by several scripting languages distributed with - # that OS release. - - if 'ARCHFLAGS' in os.environ: - arch = os.environ['ARCHFLAGS'] - for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - - flags = _config_vars[key] - flags = re.sub('-arch\s+\w+\s', ' ', flags) - flags = flags + ' ' + arch - _config_vars[key] = flags - - # If we're on OSX 10.5 or later and the user tries to - # compiles an extension using an SDK that is not present - # on the current machine it is better to not use an SDK - # than to fail. - # - # The major usecase for this is users using a Python.org - # binary installer on OSX 10.6: that installer uses - # the 10.4u SDK, but that SDK is not installed by default - # when you install Xcode. - # - m = re.search('-isysroot\s+(\S+)', _config_vars['CFLAGS']) - if m is not None: - sdk = m.group(1) - if not os.path.exists(sdk): - for key in ('LDFLAGS', 'BASECFLAGS', 'LDSHARED', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - - flags = _config_vars[key] - flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags) - _config_vars[key] = flags + import _osx_support + _osx_support.customize_config_vars(_config_vars) if args: vals = [] diff --git a/lib-python/2.7/distutils/tests/test_build_ext.py b/lib-python/2.7/distutils/tests/test_build_ext.py --- a/lib-python/2.7/distutils/tests/test_build_ext.py +++ b/lib-python/2.7/distutils/tests/test_build_ext.py @@ -77,8 +77,9 @@ self.assertEqual(xx.foo(2, 5), 7) self.assertEqual(xx.foo(13,15), 28) self.assertEqual(xx.new().demo(), None) - doc = 'This is a template module just for instruction.' - self.assertEqual(xx.__doc__, doc) + if test_support.HAVE_DOCSTRINGS: + doc = 'This is a template module just for instruction.' + self.assertEqual(xx.__doc__, doc) self.assertTrue(isinstance(xx.Null(), xx.Null)) self.assertTrue(isinstance(xx.Str(), xx.Str)) diff --git a/lib-python/2.7/distutils/tests/test_dir_util.py b/lib-python/2.7/distutils/tests/test_dir_util.py --- a/lib-python/2.7/distutils/tests/test_dir_util.py +++ b/lib-python/2.7/distutils/tests/test_dir_util.py @@ -101,6 +101,24 @@ remove_tree(self.root_target, verbose=0) remove_tree(self.target2, verbose=0) + def test_copy_tree_skips_nfs_temp_files(self): + mkpath(self.target, verbose=0) + + a_file = os.path.join(self.target, 'ok.txt') + nfs_file = os.path.join(self.target, '.nfs123abc') + for f in a_file, nfs_file: + fh = open(f, 'w') + try: + fh.write('some content') + finally: + fh.close() + + copy_tree(self.target, self.target2) + self.assertEqual(os.listdir(self.target2), ['ok.txt']) + + remove_tree(self.root_target, verbose=0) + remove_tree(self.target2, verbose=0) + def test_ensure_relative(self): if os.sep == '/': self.assertEqual(ensure_relative('/home/foo'), 'home/foo') diff --git a/lib-python/2.7/distutils/tests/test_msvc9compiler.py b/lib-python/2.7/distutils/tests/test_msvc9compiler.py --- a/lib-python/2.7/distutils/tests/test_msvc9compiler.py +++ b/lib-python/2.7/distutils/tests/test_msvc9compiler.py @@ -104,7 +104,7 @@ unittest.TestCase): def test_no_compiler(self): - # makes sure query_vcvarsall throws + # makes sure query_vcvarsall raises # a DistutilsPlatformError if the compiler # is not found from distutils.msvc9compiler import query_vcvarsall diff --git a/lib-python/2.7/distutils/tests/test_register.py b/lib-python/2.7/distutils/tests/test_register.py --- a/lib-python/2.7/distutils/tests/test_register.py +++ b/lib-python/2.7/distutils/tests/test_register.py @@ -1,6 +1,5 @@ # -*- encoding: utf8 -*- """Tests for distutils.command.register.""" -import sys import os import unittest import getpass @@ -11,11 +10,14 @@ from distutils.command import register as register_module from distutils.command.register import register -from distutils.core import Distribution from distutils.errors import DistutilsSetupError -from distutils.tests import support -from distutils.tests.test_config import PYPIRC, PyPIRCCommandTestCase +from distutils.tests.test_config import PyPIRCCommandTestCase + +try: + import docutils +except ImportError: + docutils = None PYPIRC_NOPASSWORD = """\ [distutils] @@ -192,6 +194,7 @@ self.assertEqual(headers['Content-length'], '290') self.assertTrue('tarek' in req.data) + @unittest.skipUnless(docutils is not None, 'needs docutils') def test_strict(self): # testing the script option # when on, the register command stops if @@ -204,13 +207,6 @@ cmd.strict = 1 self.assertRaises(DistutilsSetupError, cmd.run) - # we don't test the reSt feature if docutils - # is not installed - try: - import docutils - except ImportError: - return - # metadata are OK but long_description is broken metadata = {'url': 'xxx', 'author': 'xxx', 'author_email': u'??x??x??', @@ -264,6 +260,21 @@ finally: del register_module.raw_input + @unittest.skipUnless(docutils is not None, 'needs docutils') + def test_register_invalid_long_description(self): + description = ':funkie:`str`' # mimic Sphinx-specific markup + metadata = {'url': 'xxx', 'author': 'xxx', + 'author_email': 'xxx', + 'name': 'xxx', 'version': 'xxx', + 'long_description': description} + cmd = self._get_cmd(metadata) + cmd.ensure_finalized() + cmd.strict = True + inputs = RawInputs('2', 'tarek', 'tarek at ziade.org') + register_module.raw_input = inputs + self.addCleanup(delattr, register_module, 'raw_input') + self.assertRaises(DistutilsSetupError, cmd.run) + def test_check_metadata_deprecated(self): # makes sure make_metadata is deprecated cmd = self._get_cmd() diff --git a/lib-python/2.7/distutils/tests/test_sdist.py b/lib-python/2.7/distutils/tests/test_sdist.py --- a/lib-python/2.7/distutils/tests/test_sdist.py +++ b/lib-python/2.7/distutils/tests/test_sdist.py @@ -91,9 +91,8 @@ @unittest.skipUnless(zlib, "requires zlib") def test_prune_file_list(self): - # this test creates a package with some vcs dirs in it - # and launch sdist to make sure they get pruned - # on all systems + # this test creates a project with some VCS dirs and an NFS rename + # file, then launches sdist to check they get pruned on all systems # creating VCS directories with some files in them os.mkdir(join(self.tmp_dir, 'somecode', '.svn')) @@ -107,6 +106,8 @@ self.write_file((self.tmp_dir, 'somecode', '.git', 'ok'), 'xxx') + self.write_file((self.tmp_dir, 'somecode', '.nfs0001'), 'xxx') + # now building a sdist dist, cmd = self.get_cmd() diff --git a/lib-python/2.7/distutils/tests/test_sysconfig.py b/lib-python/2.7/distutils/tests/test_sysconfig.py --- a/lib-python/2.7/distutils/tests/test_sysconfig.py +++ b/lib-python/2.7/distutils/tests/test_sysconfig.py @@ -72,6 +72,35 @@ 'OTHER': 'foo'}) + def test_sysconfig_module(self): + import sysconfig as global_sysconfig + self.assertEqual(global_sysconfig.get_config_var('CFLAGS'), sysconfig.get_config_var('CFLAGS')) + self.assertEqual(global_sysconfig.get_config_var('LDFLAGS'), sysconfig.get_config_var('LDFLAGS')) + + @unittest.skipIf(sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'),'compiler flags customized') + def test_sysconfig_compiler_vars(self): + # On OS X, binary installers support extension module building on + # various levels of the operating system with differing Xcode + # configurations. This requires customization of some of the + # compiler configuration directives to suit the environment on + # the installed machine. Some of these customizations may require + # running external programs and, so, are deferred until needed by + # the first extension module build. With Python 3.3, only + # the Distutils version of sysconfig is used for extension module + # builds, which happens earlier in the Distutils tests. This may + # cause the following tests to fail since no tests have caused + # the global version of sysconfig to call the customization yet. + # The solution for now is to simply skip this test in this case. + # The longer-term solution is to only have one version of sysconfig. + + import sysconfig as global_sysconfig + if sysconfig.get_config_var('CUSTOMIZED_OSX_COMPILER'): + return + self.assertEqual(global_sysconfig.get_config_var('LDSHARED'), sysconfig.get_config_var('LDSHARED')) + self.assertEqual(global_sysconfig.get_config_var('CC'), sysconfig.get_config_var('CC')) + + + def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(SysconfigTestCase)) diff --git a/lib-python/2.7/distutils/unixccompiler.py b/lib-python/2.7/distutils/unixccompiler.py --- a/lib-python/2.7/distutils/unixccompiler.py +++ b/lib-python/2.7/distutils/unixccompiler.py @@ -26,6 +26,9 @@ DistutilsExecError, CompileError, LibError, LinkError from distutils import log +if sys.platform == 'darwin': + import _osx_support + # XXX Things not currently handled: # * optimization/debug/warning flags; we just use whatever's in Python's # Makefile and live with it. Is this adequate? If not, we might @@ -41,68 +44,6 @@ # should just happily stuff them into the preprocessor/compiler/linker # options and carry on. -def _darwin_compiler_fixup(compiler_so, cc_args): - """ - This function will strip '-isysroot PATH' and '-arch ARCH' from the - compile flags if the user has specified one them in extra_compile_flags. - - This is needed because '-arch ARCH' adds another architecture to the - build, without a way to remove an architecture. Furthermore GCC will - barf if multiple '-isysroot' arguments are present. - """ - stripArch = stripSysroot = 0 - - compiler_so = list(compiler_so) - kernel_version = os.uname()[2] # 8.4.3 - major_version = int(kernel_version.split('.')[0]) - - if major_version < 8: - # OSX before 10.4.0, these don't support -arch and -isysroot at - # all. - stripArch = stripSysroot = True - else: - stripArch = '-arch' in cc_args - stripSysroot = '-isysroot' in cc_args - - if stripArch or 'ARCHFLAGS' in os.environ: - while 1: - try: - index = compiler_so.index('-arch') - # Strip this argument and the next one: - del compiler_so[index:index+2] - except ValueError: - break - - if 'ARCHFLAGS' in os.environ and not stripArch: - # User specified different -arch flags in the environ, - # see also distutils.sysconfig - compiler_so = compiler_so + os.environ['ARCHFLAGS'].split() - - if stripSysroot: - try: - index = compiler_so.index('-isysroot') - # Strip this argument and the next one: - del compiler_so[index:index+2] - except ValueError: - pass - - # Check if the SDK that is used during compilation actually exists, - # the universal build requires the usage of a universal SDK and not all - # users have that installed by default. - sysroot = None - if '-isysroot' in cc_args: - idx = cc_args.index('-isysroot') - sysroot = cc_args[idx+1] - elif '-isysroot' in compiler_so: - idx = compiler_so.index('-isysroot') - sysroot = compiler_so[idx+1] - - if sysroot and not os.path.isdir(sysroot): - log.warn("Compiling with an SDK that doesn't seem to exist: %s", - sysroot) - log.warn("Please check your Xcode installation") - - return compiler_so class UnixCCompiler(CCompiler): @@ -172,7 +113,8 @@ def _compile(self, obj, src, ext, cc_args, extra_postargs, pp_opts): compiler_so = self.compiler_so if sys.platform == 'darwin': - compiler_so = _darwin_compiler_fixup(compiler_so, cc_args + extra_postargs) + compiler_so = _osx_support.compiler_fixup(compiler_so, + cc_args + extra_postargs) try: self.spawn(compiler_so + cc_args + [src, '-o', obj] + extra_postargs) @@ -251,7 +193,7 @@ linker[i] = self.compiler_cxx[i] if sys.platform == 'darwin': - linker = _darwin_compiler_fixup(linker, ld_args) + linker = _osx_support.compiler_fixup(linker, ld_args) self.spawn(linker + ld_args) except DistutilsExecError, msg: diff --git a/lib-python/2.7/distutils/util.py b/lib-python/2.7/distutils/util.py --- a/lib-python/2.7/distutils/util.py +++ b/lib-python/2.7/distutils/util.py @@ -51,6 +51,10 @@ return 'win-ia64' return sys.platform + # Set for cross builds explicitly + if "_PYTHON_HOST_PLATFORM" in os.environ: + return os.environ["_PYTHON_HOST_PLATFORM"] + if os.name != "posix" or not hasattr(os, 'uname'): # XXX what about the architecture? NT is Intel or Alpha, # Mac OS is M68k or PPC, etc. @@ -93,94 +97,10 @@ if m: release = m.group() elif osname[:6] == "darwin": - # - # For our purposes, we'll assume that the system version from - # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set - # to. This makes the compatibility story a bit more sane because the - # machine is going to compile and link as if it were - # MACOSX_DEPLOYMENT_TARGET. - from distutils.sysconfig import get_config_vars - cfgvars = get_config_vars() - - macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET') - - if 1: - # Always calculate the release of the running machine, - # needed to determine if we can build fat binaries or not. - - macrelease = macver - # Get the system version. Reading this plist is a documented - # way to get the system version (see the documentation for - # the Gestalt Manager) - try: - f = open('/System/Library/CoreServices/SystemVersion.plist') - except IOError: - # We're on a plain darwin box, fall back to the default - # behaviour. - pass - else: - try: - m = re.search( - r'ProductUserVisibleVersion\s*' + - r'(.*?)', f.read()) - if m is not None: - macrelease = '.'.join(m.group(1).split('.')[:2]) - # else: fall back to the default behaviour - finally: - f.close() - - if not macver: - macver = macrelease - - if macver: - from distutils.sysconfig import get_config_vars - release = macver - osname = "macosx" - - if (macrelease + '.') >= '10.4.' and \ - '-arch' in get_config_vars().get('CFLAGS', '').strip(): - # The universal build will build fat binaries, but not on - # systems before 10.4 - # - # Try to detect 4-way universal builds, those have machine-type - # 'universal' instead of 'fat'. - - machine = 'fat' - cflags = get_config_vars().get('CFLAGS') - - archs = re.findall('-arch\s+(\S+)', cflags) - archs = tuple(sorted(set(archs))) - - if len(archs) == 1: - machine = archs[0] - elif archs == ('i386', 'ppc'): - machine = 'fat' - elif archs == ('i386', 'x86_64'): - machine = 'intel' - elif archs == ('i386', 'ppc', 'x86_64'): - machine = 'fat3' - elif archs == ('ppc64', 'x86_64'): - machine = 'fat64' - elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'): - machine = 'universal' - else: - raise ValueError( - "Don't know machine value for archs=%r"%(archs,)) - - elif machine == 'i386': - # On OSX the machine type returned by uname is always the - # 32-bit variant, even if the executable architecture is - # the 64-bit variant - if sys.maxint >= 2**32: - machine = 'x86_64' - - elif machine in ('PowerPC', 'Power_Macintosh'): - # Pick a sane name for the PPC architecture. - machine = 'ppc' - - # See 'i386' case - if sys.maxint >= 2**32: - machine = 'ppc64' + import _osx_support, distutils.sysconfig + osname, release, machine = _osx_support.get_platform_osx( + distutils.sysconfig.get_config_vars(), + osname, release, machine) return "%s-%s-%s" % (osname, release, machine) diff --git a/lib-python/2.7/doctest.py b/lib-python/2.7/doctest.py --- a/lib-python/2.7/doctest.py +++ b/lib-python/2.7/doctest.py @@ -2314,7 +2314,8 @@ return "Doctest: " + self._dt_test.name class SkipDocTestCase(DocTestCase): - def __init__(self): + def __init__(self, module): + self.module = module DocTestCase.__init__(self, None) def setUp(self): @@ -2324,7 +2325,10 @@ pass def shortDescription(self): - return "Skipping tests from %s" % module.__name__ + return "Skipping tests from %s" % self.module.__name__ + + __str__ = shortDescription + def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, **options): @@ -2372,12 +2376,17 @@ if not tests and sys.flags.optimize >=2: # Skip doctests when running with -O2 suite = unittest.TestSuite() - suite.addTest(SkipDocTestCase()) + suite.addTest(SkipDocTestCase(module)) return suite elif not tests: # Why do we want to do this? Because it reveals a bug that might # otherwise be hidden. - raise ValueError(module, "has no tests") + # It is probably a bug that this exception is not also raised if the + # number of doctest examples in tests is zero (i.e. if no doctest + # examples were found). However, we should probably not be raising + # an exception at all here, though it is too late to make this change + # for a maintenance release. See also issue #14649. + raise ValueError(module, "has no docstrings") tests.sort() suite = unittest.TestSuite() diff --git a/lib-python/2.7/email/_parseaddr.py b/lib-python/2.7/email/_parseaddr.py --- a/lib-python/2.7/email/_parseaddr.py +++ b/lib-python/2.7/email/_parseaddr.py @@ -13,7 +13,7 @@ 'quote', ] -import time +import time, calendar SPACE = ' ' EMPTYSTRING = '' @@ -150,13 +150,13 @@ def mktime_tz(data): - """Turn a 10-tuple as returned by parsedate_tz() into a UTC timestamp.""" + """Turn a 10-tuple as returned by parsedate_tz() into a POSIX timestamp.""" if data[9] is None: # No zone info, so localtime is better assumption than GMT return time.mktime(data[:8] + (-1,)) else: - t = time.mktime(data[:8] + (0,)) - return t - data[9] - time.timezone + t = calendar.timegm(data) + return t - data[9] def quote(str): diff --git a/lib-python/2.7/email/base64mime.py b/lib-python/2.7/email/base64mime.py --- a/lib-python/2.7/email/base64mime.py +++ b/lib-python/2.7/email/base64mime.py @@ -130,7 +130,7 @@ verbatim (this is the default). Each line of encoded text will end with eol, which defaults to "\\n". Set - this to "\r\n" if you will be using the result of this function directly + this to "\\r\\n" if you will be using the result of this function directly in an email. """ if not s: diff --git a/lib-python/2.7/email/feedparser.py b/lib-python/2.7/email/feedparser.py --- a/lib-python/2.7/email/feedparser.py +++ b/lib-python/2.7/email/feedparser.py @@ -13,7 +13,7 @@ data. When you have no more data to push into the parser, call .close(). This completes the parsing and returns the root message object. -The other advantage of this parser is that it will never throw a parsing +The other advantage of this parser is that it will never raise a parsing exception. Instead, when it finds something unexpected, it adds a 'defect' to the current message. Defects are just instances that live on the message object's .defects attribute. @@ -214,7 +214,7 @@ # supposed to see in the body of the message. self._parse_headers(headers) # Headers-only parsing is a backwards compatibility hack, which was - # necessary in the older parser, which could throw errors. All + # necessary in the older parser, which could raise errors. All # remaining lines in the input are thrown into the message body. if self._headersonly: lines = [] diff --git a/lib-python/2.7/email/generator.py b/lib-python/2.7/email/generator.py --- a/lib-python/2.7/email/generator.py +++ b/lib-python/2.7/email/generator.py @@ -212,7 +212,11 @@ msg.set_boundary(boundary) # If there's a preamble, write it out, with a trailing CRLF if msg.preamble is not None: - print >> self._fp, msg.preamble + if self._mangle_from_: + preamble = fcre.sub('>From ', msg.preamble) + else: + preamble = msg.preamble + print >> self._fp, preamble # dash-boundary transport-padding CRLF print >> self._fp, '--' + boundary # body-part @@ -230,7 +234,11 @@ self._fp.write('\n--' + boundary + '--') if msg.epilogue is not None: print >> self._fp - self._fp.write(msg.epilogue) + if self._mangle_from_: + epilogue = fcre.sub('>From ', msg.epilogue) + else: + epilogue = msg.epilogue + self._fp.write(epilogue) def _handle_multipart_signed(self, msg): # The contents of signed parts has to stay unmodified in order to keep diff --git a/lib-python/2.7/email/test/test_email.py b/lib-python/2.7/email/test/test_email.py --- a/lib-python/2.7/email/test/test_email.py +++ b/lib-python/2.7/email/test/test_email.py @@ -9,6 +9,7 @@ import difflib import unittest import warnings +import textwrap from cStringIO import StringIO import email @@ -948,6 +949,28 @@ Blah blah blah """) + def test_mangle_from_in_preamble_and_epilog(self): + s = StringIO() + g = Generator(s, mangle_from_=True) + msg = email.message_from_string(textwrap.dedent("""\ + From: foo at bar.com + Mime-Version: 1.0 + Content-Type: multipart/mixed; boundary=XXX + + From somewhere unknown + + --XXX + Content-Type: text/plain + + foo + + --XXX-- + + From somewhere unknowable + """)) + g.flatten(msg) + self.assertEqual(len([1 for x in s.getvalue().split('\n') + if x.startswith('>From ')]), 2) # Test the basic MIMEAudio class @@ -2262,6 +2285,12 @@ eq(time.localtime(t)[:6], timetup[:6]) eq(int(time.strftime('%Y', timetup[:9])), 2003) + def test_mktime_tz(self): + self.assertEqual(Utils.mktime_tz((1970, 1, 1, 0, 0, 0, + -1, -1, -1, 0)), 0) + self.assertEqual(Utils.mktime_tz((1970, 1, 1, 0, 0, 0, + -1, -1, -1, 1234)), -1234) + def test_parsedate_y2k(self): """Test for parsing a date with a two-digit year. diff --git a/lib-python/2.7/email/test/test_email_renamed.py b/lib-python/2.7/email/test/test_email_renamed.py --- a/lib-python/2.7/email/test/test_email_renamed.py +++ b/lib-python/2.7/email/test/test_email_renamed.py @@ -994,6 +994,38 @@ eq(msg.get_payload(), '+vv8/f7/') eq(msg.get_payload(decode=True), bytes) + def test_binary_body_with_encode_7or8bit(self): + # Issue 17171. + bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff' + msg = MIMEApplication(bytesdata, _encoder=encoders.encode_7or8bit) + # Treated as a string, this will be invalid code points. + self.assertEqual(msg.get_payload(), bytesdata) + self.assertEqual(msg.get_payload(decode=True), bytesdata) + self.assertEqual(msg['Content-Transfer-Encoding'], '8bit') + s = StringIO() + g = Generator(s) + g.flatten(msg) + wireform = s.getvalue() + msg2 = email.message_from_string(wireform) + self.assertEqual(msg.get_payload(), bytesdata) + self.assertEqual(msg2.get_payload(decode=True), bytesdata) + self.assertEqual(msg2['Content-Transfer-Encoding'], '8bit') + + def test_binary_body_with_encode_noop(self): + # Issue 16564: This does not produce an RFC valid message, since to be + # valid it should have a CTE of binary. But the below works, and is + # documented as working this way. + bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff' + msg = MIMEApplication(bytesdata, _encoder=encoders.encode_noop) + self.assertEqual(msg.get_payload(), bytesdata) + self.assertEqual(msg.get_payload(decode=True), bytesdata) + s = StringIO() + g = Generator(s) + g.flatten(msg) + wireform = s.getvalue() + msg2 = email.message_from_string(wireform) + self.assertEqual(msg.get_payload(), bytesdata) + self.assertEqual(msg2.get_payload(decode=True), bytesdata) # Test the basic MIMEText class diff --git a/lib-python/2.7/email/utils.py b/lib-python/2.7/email/utils.py --- a/lib-python/2.7/email/utils.py +++ b/lib-python/2.7/email/utils.py @@ -63,7 +63,7 @@ """Decodes a base64 string. This function is equivalent to base64.decodestring and it's retained only - for backward compatibility. It used to remove the last \n of the decoded + for backward compatibility. It used to remove the last \\n of the decoded string, if it had any (see issue 7143). """ if not s: @@ -73,7 +73,7 @@ def fix_eols(s): - """Replace all line-ending characters with \r\n.""" + """Replace all line-ending characters with \\r\\n.""" # Fix newlines with no preceding carriage return s = re.sub(r'(? self.extrasize: - self._read(readsize) - readsize = min(self.max_read_chunk, readsize * 2) - except EOFError: - if size > self.extrasize: - size = self.extrasize + while size > self.extrasize: + if not self._read(readsize): + if size > self.extrasize: + size = self.extrasize + break + readsize = min(self.max_read_chunk, readsize * 2) offset = self.offset - self.extrastart chunk = self.extrabuf[offset: offset + size] @@ -272,7 +274,7 @@ def _read(self, size=1024): if self.fileobj is None: - raise EOFError, "Reached EOF" + return False if self._new_member: # If the _new_member flag is set, we have to @@ -283,7 +285,7 @@ pos = self.fileobj.tell() # Save current position self.fileobj.seek(0, 2) # Seek to end of file if pos == self.fileobj.tell(): - raise EOFError, "Reached EOF" + return False else: self.fileobj.seek( pos ) # Return to original position @@ -300,9 +302,10 @@ if buf == "": uncompress = self.decompress.flush() + self.fileobj.seek(-len(self.decompress.unused_data), 1) self._read_eof() self._add_read_data( uncompress ) - raise EOFError, 'Reached EOF' + return False uncompress = self.decompress.decompress(buf) self._add_read_data( uncompress ) @@ -312,13 +315,14 @@ # so seek back to the start of the unused data, finish up # this member, and read a new gzip header. # (The number of bytes to seek back is the length of the unused - # data, minus 8 because _read_eof() will rewind a further 8 bytes) - self.fileobj.seek( -len(self.decompress.unused_data)+8, 1) + # data) + self.fileobj.seek(-len(self.decompress.unused_data), 1) # Check the CRC and file size, and set the flag so we read # a new member on the next call self._read_eof() self._new_member = True + return True def _add_read_data(self, data): self.crc = zlib.crc32(data, self.crc) & 0xffffffffL @@ -329,14 +333,11 @@ self.size = self.size + len(data) def _read_eof(self): - # We've read to the end of the file, so we have to rewind in order - # to reread the 8 bytes containing the CRC and the file size. + # We've read to the end of the file. # We check the that the computed CRC and size of the # uncompressed data matches the stored values. Note that the size # stored is the true file size mod 2**32. - self.fileobj.seek(-8, 1) - crc32 = read32(self.fileobj) - isize = read32(self.fileobj) # may exceed 2GB + crc32, isize = struct.unpack(" Largest of the nsmallest - for elem in it: - if cmp_lt(elem, los): - insort(result, elem) - pop() - los = result[-1] + it = iter(iterable) + result = list(islice(it, n)) + if not result: return result - # An alternative approach manifests the whole iterable in memory but - # saves comparisons by heapifying all at once. Also, saves time - # over bisect.insort() which has O(n) data movement time for every - # insertion. Finding the n smallest of an m length iterable requires - # O(m) + O(n log m) comparisons. - h = list(iterable) - heapify(h) - return map(heappop, repeat(h, min(n, len(h)))) + _heapify_max(result) + _heappushpop = _heappushpop_max + for elem in it: + _heappushpop(result, elem) + result.sort() + return result # 'heap' is a heap at all indices >= startpos, except possibly for pos. pos # is the index of a leaf with a possibly out-of-order value. Restore the @@ -314,6 +312,42 @@ heap[pos] = newitem _siftdown(heap, startpos, pos) +def _siftdown_max(heap, startpos, pos): + 'Maxheap variant of _siftdown' + newitem = heap[pos] + # Follow the path to the root, moving parents down until finding a place + # newitem fits. + while pos > startpos: + parentpos = (pos - 1) >> 1 + parent = heap[parentpos] + if cmp_lt(parent, newitem): + heap[pos] = parent + pos = parentpos + continue + break + heap[pos] = newitem + +def _siftup_max(heap, pos): + 'Maxheap variant of _siftup' + endpos = len(heap) + startpos = pos + newitem = heap[pos] + # Bubble up the larger child until hitting a leaf. + childpos = 2*pos + 1 # leftmost child position + while childpos < endpos: + # Set childpos to index of larger child. + rightpos = childpos + 1 + if rightpos < endpos and not cmp_lt(heap[rightpos], heap[childpos]): + childpos = rightpos + # Move the larger child up. + heap[pos] = heap[childpos] + pos = childpos + childpos = 2*pos + 1 + # The leaf at pos is empty now. Put newitem there, and bubble it up + # to its final resting place (by sifting its parents down). + heap[pos] = newitem + _siftdown_max(heap, startpos, pos) + # If available, use C implementation try: from _heapq import * diff --git a/lib-python/2.7/httplib.py b/lib-python/2.7/httplib.py --- a/lib-python/2.7/httplib.py +++ b/lib-python/2.7/httplib.py @@ -362,7 +362,9 @@ def _read_status(self): # Initialize with Simple-Response defaults - line = self.fp.readline() + line = self.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise LineTooLong("header line") if self.debuglevel > 0: print "reply:", repr(line) if not line: @@ -545,7 +547,11 @@ if self.length is None: s = self.fp.read() else: - s = self._safe_read(self.length) + try: + s = self._safe_read(self.length) + except IncompleteRead: + self.close() + raise self.length = 0 self.close() # we read everything return s @@ -559,10 +565,15 @@ # connection, and the user is reading more bytes than will be provided # (for example, reading in 1k chunks) s = self.fp.read(amt) + if not s: + # Ideally, we would raise IncompleteRead if the content-length + # wasn't satisfied, but it might break compatibility. + self.close() if self.length is not None: self.length -= len(s) if not self.length: self.close() + return s def _read_chunked(self, amt): @@ -748,7 +759,11 @@ line = response.fp.readline(_MAXLINE + 1) if len(line) > _MAXLINE: raise LineTooLong("header line") - if line == '\r\n': break + if not line: + # for sites which EOF without sending trailer + break + if line == '\r\n': + break def connect(self): @@ -985,7 +1000,7 @@ self.putrequest(method, url, **skips) - if body and ('content-length' not in header_names): + if body is not None and 'content-length' not in header_names: self._set_content_length(body) for hdr, value in headers.iteritems(): self.putheader(hdr, value) @@ -1058,7 +1073,7 @@ if port == 0: port = None - # Note that we may pass an empty string as the host; this will throw + # Note that we may pass an empty string as the host; this will raise # an error when we attempt to connect. Presumably, the client code # will call connect before then, with a proper host. self._setup(self._connection_class(host, port, strict)) diff --git a/lib-python/2.7/idlelib/CallTips.py b/lib-python/2.7/idlelib/CallTips.py --- a/lib-python/2.7/idlelib/CallTips.py +++ b/lib-python/2.7/idlelib/CallTips.py @@ -71,16 +71,16 @@ if not sur_paren: return hp.set_index(sur_paren[0]) - name = hp.get_expression() - if not name or (not evalfuncs and name.find('(') != -1): + expression = hp.get_expression() + if not expression or (not evalfuncs and expression.find('(') != -1): return - arg_text = self.fetch_tip(name) + arg_text = self.fetch_tip(expression) if not arg_text: return self.calltip = self._make_calltip_window() self.calltip.showtip(arg_text, sur_paren[0], sur_paren[1]) - def fetch_tip(self, name): + def fetch_tip(self, expression): """Return the argument list and docstring of a function or class If there is a Python subprocess, get the calltip there. Otherwise, @@ -96,23 +96,27 @@ """ try: rpcclt = self.editwin.flist.pyshell.interp.rpcclt - except: + except AttributeError: rpcclt = None if rpcclt: return rpcclt.remotecall("exec", "get_the_calltip", - (name,), {}) + (expression,), {}) else: - entity = self.get_entity(name) + entity = self.get_entity(expression) return get_arg_text(entity) - def get_entity(self, name): - "Lookup name in a namespace spanning sys.modules and __main.dict__" - if name: + def get_entity(self, expression): + """Return the object corresponding to expression evaluated + in a namespace spanning sys.modules and __main.dict__. + """ + if expression: namespace = sys.modules.copy() namespace.update(__main__.__dict__) try: - return eval(name, namespace) - except (NameError, AttributeError): + return eval(expression, namespace) + except BaseException: + # An uncaught exception closes idle, and eval can raise any + # exception, especially if user classes are involved. return None def _find_constructor(class_ob): @@ -127,9 +131,10 @@ return None def get_arg_text(ob): - """Get a string describing the arguments for the given object""" + """Get a string describing the arguments for the given object, + only if it is callable.""" arg_text = "" - if ob is not None: + if ob is not None and hasattr(ob, '__call__'): arg_offset = 0 if type(ob) in (types.ClassType, types.TypeType): # Look for the highest __init__ in the class chain. diff --git a/lib-python/2.7/idlelib/ColorDelegator.py b/lib-python/2.7/idlelib/ColorDelegator.py --- a/lib-python/2.7/idlelib/ColorDelegator.py +++ b/lib-python/2.7/idlelib/ColorDelegator.py @@ -20,10 +20,11 @@ # 1st 'file' colorized normal, 2nd as builtin, 3rd as string builtin = r"([^.'\"\\#]\b|^)" + any("BUILTIN", builtinlist) + r"\b" comment = any("COMMENT", [r"#[^\n]*"]) - sqstring = r"(\b[rRuU])?'[^'\\\n]*(\\.[^'\\\n]*)*'?" - dqstring = r'(\b[rRuU])?"[^"\\\n]*(\\.[^"\\\n]*)*"?' - sq3string = r"(\b[rRuU])?'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?" - dq3string = r'(\b[rRuU])?"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?' + stringprefix = r"(\br|u|ur|R|U|UR|Ur|uR|b|B|br|Br|bR|BR)?" + sqstring = stringprefix + r"'[^'\\\n]*(\\.[^'\\\n]*)*'?" + dqstring = stringprefix + r'"[^"\\\n]*(\\.[^"\\\n]*)*"?' + sq3string = stringprefix + r"'''[^'\\]*((\\.|'(?!''))[^'\\]*)*(''')?" + dq3string = stringprefix + r'"""[^"\\]*((\\.|"(?!""))[^"\\]*)*(""")?' string = any("STRING", [sq3string, dq3string, sqstring, dqstring]) return kw + "|" + builtin + "|" + comment + "|" + string +\ "|" + any("SYNC", [r"\n"]) diff --git a/lib-python/2.7/idlelib/EditorWindow.py b/lib-python/2.7/idlelib/EditorWindow.py --- a/lib-python/2.7/idlelib/EditorWindow.py +++ b/lib-python/2.7/idlelib/EditorWindow.py @@ -172,13 +172,13 @@ 'recent-files.lst') self.text_frame = text_frame = Frame(top) self.vbar = vbar = Scrollbar(text_frame, name='vbar') - self.width = idleConf.GetOption('main','EditorWindow','width') + self.width = idleConf.GetOption('main','EditorWindow','width', type='int') text_options = { 'name': 'text', 'padx': 5, 'wrap': 'none', 'width': self.width, - 'height': idleConf.GetOption('main', 'EditorWindow', 'height')} + 'height': idleConf.GetOption('main', 'EditorWindow', 'height', type='int')} if TkVersion >= 8.5: # Starting with tk 8.5 we have to set the new tabstyle option # to 'wordprocessor' to achieve the same display of tabs as in @@ -255,7 +255,8 @@ if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'): fontWeight='bold' text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'), - idleConf.GetOption('main', 'EditorWindow', 'font-size'), + idleConf.GetOption('main', 'EditorWindow', + 'font-size', type='int'), fontWeight)) text_frame.pack(side=LEFT, fill=BOTH, expand=1) text.pack(side=TOP, fill=BOTH, expand=1) @@ -470,7 +471,6 @@ rmenu = None def right_menu_event(self, event): - self.text.tag_remove("sel", "1.0", "end") self.text.mark_set("insert", "@%d,%d" % (event.x, event.y)) if not self.rmenu: self.make_rmenu() @@ -479,23 +479,52 @@ iswin = sys.platform[:3] == 'win' if iswin: self.text.config(cursor="arrow") + + for label, eventname, verify_state in self.rmenu_specs: + if verify_state is None: + continue + state = getattr(self, verify_state)() + rmenu.entryconfigure(label, state=state) + rmenu.tk_popup(event.x_root, event.y_root) if iswin: self.text.config(cursor="ibeam") rmenu_specs = [ - # ("Label", "<>"), ... - ("Close", "<>"), # Example + # ("Label", "<>", "statefuncname"), ... + ("Close", "<>", None), # Example ] def make_rmenu(self): rmenu = Menu(self.text, tearoff=0) - for label, eventname in self.rmenu_specs: - def command(text=self.text, eventname=eventname): - text.event_generate(eventname) - rmenu.add_command(label=label, command=command) + for label, eventname, _ in self.rmenu_specs: + if label is not None: + def command(text=self.text, eventname=eventname): + text.event_generate(eventname) + rmenu.add_command(label=label, command=command) + else: + rmenu.add_separator() self.rmenu = rmenu + def rmenu_check_cut(self): + return self.rmenu_check_copy() + + def rmenu_check_copy(self): + try: + indx = self.text.index('sel.first') + except TclError: + return 'disabled' + else: + return 'normal' if indx else 'disabled' + + def rmenu_check_paste(self): + try: + self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD') + except TclError: + return 'disabled' + else: + return 'normal' + def about_dialog(self, event=None): aboutDialog.AboutDialog(self.top,'About IDLE') @@ -735,7 +764,8 @@ if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'): fontWeight='bold' self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'), - idleConf.GetOption('main','EditorWindow','font-size'), + idleConf.GetOption('main','EditorWindow','font-size', + type='int'), fontWeight)) def RemoveKeybindings(self): @@ -856,7 +886,7 @@ # for each edit window instance, construct the recent files menu for instance in self.top.instance_dict.keys(): menu = instance.recent_files_menu - menu.delete(1, END) # clear, and rebuild: + menu.delete(0, END) # clear, and rebuild: for i, file_name in enumerate(rf_list): file_name = file_name.rstrip() # zap \n # make unicode string to display non-ASCII chars correctly @@ -1581,7 +1611,7 @@ try: try: _tokenize.tokenize(self.readline, self.tokeneater) - except _tokenize.TokenError: + except (_tokenize.TokenError, SyntaxError): # since we cut off the tokenizer early, we can trigger # spurious errors pass diff --git a/lib-python/2.7/idlelib/FormatParagraph.py b/lib-python/2.7/idlelib/FormatParagraph.py --- a/lib-python/2.7/idlelib/FormatParagraph.py +++ b/lib-python/2.7/idlelib/FormatParagraph.py @@ -32,7 +32,8 @@ self.editwin = None def format_paragraph_event(self, event): - maxformatwidth = int(idleConf.GetOption('main','FormatParagraph','paragraph')) + maxformatwidth = int(idleConf.GetOption('main','FormatParagraph', + 'paragraph', type='int')) text = self.editwin.text first, last = self.editwin.get_selection_indices() if first and last: diff --git a/lib-python/2.7/idlelib/HyperParser.py b/lib-python/2.7/idlelib/HyperParser.py --- a/lib-python/2.7/idlelib/HyperParser.py +++ b/lib-python/2.7/idlelib/HyperParser.py @@ -232,6 +232,11 @@ pass else: # We can't continue after other types of brackets + if rawtext[pos] in "'\"": + # Scan a string prefix + while pos > 0 and rawtext[pos - 1] in "rRbBuU": + pos -= 1 + last_identifier_pos = pos break else: diff --git a/lib-python/2.7/idlelib/IOBinding.py b/lib-python/2.7/idlelib/IOBinding.py --- a/lib-python/2.7/idlelib/IOBinding.py +++ b/lib-python/2.7/idlelib/IOBinding.py @@ -7,6 +7,7 @@ import os import types +import pipes import sys import codecs import tempfile @@ -196,29 +197,33 @@ self.filename_change_hook() def open(self, event=None, editFile=None): - if self.editwin.flist: + flist = self.editwin.flist + # Save in case parent window is closed (ie, during askopenfile()). + if flist: if not editFile: filename = self.askopenfile() else: filename=editFile if filename: - # If the current window has no filename and hasn't been - # modified, we replace its contents (no loss). Otherwise - # we open a new window. But we won't replace the - # shell window (which has an interp(reter) attribute), which - # gets set to "not modified" at every new prompt. - try: - interp = self.editwin.interp - except AttributeError: - interp = None - if not self.filename and self.get_saved() and not interp: - self.editwin.flist.open(filename, self.loadfile) + # If editFile is valid and already open, flist.open will + # shift focus to its existing window. + # If the current window exists and is a fresh unnamed, + # unmodified editor window (not an interpreter shell), + # pass self.loadfile to flist.open so it will load the file + # in the current window (if the file is not already open) + # instead of a new window. + if (self.editwin and + not getattr(self.editwin, 'interp', None) and + not self.filename and + self.get_saved()): + flist.open(filename, self.loadfile) else: - self.editwin.flist.open(filename) + flist.open(filename) else: - self.text.focus_set() + if self.text: + self.text.focus_set() return "break" - # + # Code for use outside IDLE: if self.get_saved(): reply = self.maybesave() @@ -499,7 +504,7 @@ else: #no printing for this platform printPlatform = False if printPlatform: #we can try to print for this platform - command = command % filename + command = command % pipes.quote(filename) pipe = os.popen(command, "r") # things can get ugly on NT if there is no printer available. output = pipe.read().strip() diff --git a/lib-python/2.7/idlelib/NEWS.txt b/lib-python/2.7/idlelib/NEWS.txt --- a/lib-python/2.7/idlelib/NEWS.txt +++ b/lib-python/2.7/idlelib/NEWS.txt @@ -1,5 +1,38 @@ +What's New in IDLE 2.7.4? +========================= + +- Issue #15318: Prevent writing to sys.stdin. + +- Issue #13532, #15319: Check that arguments to sys.stdout.write are strings. + +- Issue # 12510: Attempt to get certain tool tips no longer crashes IDLE. + +- Issue10365: File open dialog now works instead of crashing even when + parent window is closed while dialog is open. + +- Issue 14876: use user-selected font for highlight configuration. + +- Issue #14018: Update checks for unstable system Tcl/Tk versions on OS X + to include versions shipped with OS X 10.7 and 10.8 in addition to 10.6. + +- Issue #15853: Prevent IDLE crash on OS X when opening Preferences menu + with certain versions of Tk 8.5. Initial patch by Kevin Walzer. + + +What's New in IDLE 2.7.3? +========================= + +- Issue #14409: IDLE now properly executes commands in the Shell window + when it cannot read the normal config files on startup and + has to use the built-in default key bindings. + There was previously a bug in one of the defaults. + +- Issue #3573: IDLE hangs when passing invalid command line args + (directory(ies) instead of file(s)). + + What's New in IDLE 2.7.2? -======================= +========================= *Release date: 29-May-2011* diff --git a/lib-python/2.7/idlelib/OutputWindow.py b/lib-python/2.7/idlelib/OutputWindow.py --- a/lib-python/2.7/idlelib/OutputWindow.py +++ b/lib-python/2.7/idlelib/OutputWindow.py @@ -57,7 +57,11 @@ # Our own right-button menu rmenu_specs = [ - ("Go to file/line", "<>"), + ("Cut", "<>", "rmenu_check_cut"), + ("Copy", "<>", "rmenu_check_copy"), + ("Paste", "<>", "rmenu_check_paste"), + (None, None, None), + ("Go to file/line", "<>", None), ] file_line_pats = [ diff --git a/lib-python/2.7/idlelib/PyShell.py b/lib-python/2.7/idlelib/PyShell.py --- a/lib-python/2.7/idlelib/PyShell.py +++ b/lib-python/2.7/idlelib/PyShell.py @@ -11,6 +11,7 @@ import threading import traceback import types +import io import linecache from code import InteractiveInterpreter @@ -121,8 +122,13 @@ old_hook() self.io.set_filename_change_hook(filename_changed_hook) - rmenu_specs = [("Set Breakpoint", "<>"), - ("Clear Breakpoint", "<>")] + rmenu_specs = [ + ("Cut", "<>", "rmenu_check_cut"), + ("Copy", "<>", "rmenu_check_copy"), + ("Paste", "<>", "rmenu_check_paste"), + ("Set Breakpoint", "<>", None), + ("Clear Breakpoint", "<>", None) + ] def set_breakpoint(self, lineno): text = self.text @@ -251,8 +257,8 @@ def ranges_to_linenumbers(self, ranges): lines = [] for index in range(0, len(ranges), 2): - lineno = int(float(ranges[index])) - end = int(float(ranges[index+1])) + lineno = int(float(ranges[index].string)) + end = int(float(ranges[index+1].string)) while lineno < end: lines.append(lineno) lineno += 1 @@ -313,6 +319,11 @@ "console": idleConf.GetHighlight(theme, "console"), }) + def removecolors(self): + # Don't remove shell color tags before "iomark" + for tag in self.tagdefs: + self.tag_remove(tag, "iomark", "end") + class ModifiedUndoDelegator(UndoDelegator): "Extend base class: forbid insert/delete before the I/O mark" @@ -417,7 +428,8 @@ except socket.timeout, err: self.display_no_subprocess_error() return None - self.rpcclt.register("stdin", self.tkconsole) + self.rpcclt.register("console", self.tkconsole) + self.rpcclt.register("stdin", self.tkconsole.stdin) self.rpcclt.register("stdout", self.tkconsole.stdout) self.rpcclt.register("stderr", self.tkconsole.stderr) self.rpcclt.register("flist", self.tkconsole.flist) @@ -870,13 +882,14 @@ self.save_stderr = sys.stderr self.save_stdin = sys.stdin from idlelib import IOBinding - self.stdout = PseudoFile(self, "stdout", IOBinding.encoding) - self.stderr = PseudoFile(self, "stderr", IOBinding.encoding) - self.console = PseudoFile(self, "console", IOBinding.encoding) + self.stdin = PseudoInputFile(self, "stdin", IOBinding.encoding) + self.stdout = PseudoOutputFile(self, "stdout", IOBinding.encoding) + self.stderr = PseudoOutputFile(self, "stderr", IOBinding.encoding) + self.console = PseudoOutputFile(self, "console", IOBinding.encoding) if not use_subprocess: sys.stdout = self.stdout sys.stderr = self.stderr - sys.stdin = self + sys.stdin = self.stdin # self.history = self.History(self.text) # @@ -1251,28 +1264,98 @@ if not use_subprocess: raise KeyboardInterrupt -class PseudoFile(object): + def rmenu_check_cut(self): + try: + if self.text.compare('sel.first', '<', 'iomark'): + return 'disabled' + except TclError: # no selection, so the index 'sel.first' doesn't exist + return 'disabled' + return super(PyShell, self).rmenu_check_cut() + + def rmenu_check_paste(self): + if self.text.compare('insert', '<', 'iomark'): + return 'disabled' + return super(PyShell, self).rmenu_check_paste() + +class PseudoFile(io.TextIOBase): def __init__(self, shell, tags, encoding=None): self.shell = shell self.tags = tags self.softspace = 0 - self.encoding = encoding + self._encoding = encoding - def write(self, s): - self.shell.write(s, self.tags) + @property + def encoding(self): + return self._encoding - def writelines(self, lines): - for line in lines: - self.write(line) - - def flush(self): - pass + @property + def name(self): + return '<%s>' % self.tags def isatty(self): return True +class PseudoOutputFile(PseudoFile): + + def writable(self): + return True + + def write(self, s): + if self.closed: + raise ValueError("write to closed file") + if not isinstance(s, (basestring, bytearray)): + raise TypeError('must be string, not ' + type(s).__name__) + 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 = '' + + def readable(self): + return True + + def read(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + result = self._line_buffer + self._line_buffer = '' + if size < 0: + while True: + line = self.shell.readline() + if not line: break + result += line + else: + while len(result) < size: + line = self.shell.readline() + if not line: break + result += line + self._line_buffer = result[size:] + result = result[:size] + return result + + def readline(self, size=-1): + if self.closed: + raise ValueError("read from closed file") + if size is None: + size = -1 + elif not isinstance(size, int): + raise TypeError('must be int, not ' + type(size).__name__) + line = self._line_buffer or self.shell.readline() + if size < 0: + size = len(line) + self._line_buffer = line[size:] + return line[:size] + + usage_msg = """\ USAGE: idle [-deins] [-t title] [file]* @@ -1412,8 +1495,10 @@ if enable_edit: if not (cmd or script): - for filename in args: - flist.open(filename) + for filename in args[:]: + if flist.open(filename) is None: + # filename is a directory actually, disconsider it + args.remove(filename) if not args: flist.new() if enable_shell: @@ -1456,7 +1541,8 @@ if tkversionwarning: shell.interp.runcommand(''.join(("print('", tkversionwarning, "')"))) - root.mainloop() + while flist.inversedict: # keep IDLE running while files are open. + root.mainloop() root.destroy() if __name__ == "__main__": diff --git a/lib-python/2.7/idlelib/ReplaceDialog.py b/lib-python/2.7/idlelib/ReplaceDialog.py --- a/lib-python/2.7/idlelib/ReplaceDialog.py +++ b/lib-python/2.7/idlelib/ReplaceDialog.py @@ -2,6 +2,8 @@ from idlelib import SearchEngine from idlelib.SearchDialogBase import SearchDialogBase +import re + def replace(text): root = text._root() @@ -11,6 +13,7 @@ dialog = engine._replacedialog dialog.open(text) + class ReplaceDialog(SearchDialogBase): title = "Replace Dialog" @@ -55,8 +58,22 @@ def default_command(self, event=None): if self.do_find(self.ok): - self.do_replace() - self.do_find(0) + if self.do_replace(): # Only find next match if replace succeeded. + # A bad re can cause a it to fail. + self.do_find(0) + + def _replace_expand(self, m, repl): + """ Helper function for expanding a regular expression + in the replace field, if needed. """ + if self.engine.isre(): + try: + new = m.expand(repl) + except re.error: + self.engine.report_error(repl, 'Invalid Replace Expression') + new = None + else: + new = repl + return new def replace_all(self, event=None): prog = self.engine.getprog() @@ -86,7 +103,9 @@ line, m = res chars = text.get("%d.0" % line, "%d.0" % (line+1)) orig = m.group() - new = m.expand(repl) + new = self._replace_expand(m, repl) + if new is None: + break i, j = m.span() first = "%d.%d" % (line, i) last = "%d.%d" % (line, j) @@ -103,7 +122,6 @@ text.undo_block_stop() if first and last: self.show_hit(first, last) - self.close() def do_find(self, ok=0): if not self.engine.getprog(): @@ -138,7 +156,9 @@ m = prog.match(chars, col) if not prog: return False - new = m.expand(self.replvar.get()) + new = self._replace_expand(m, self.replvar.get()) + if new is None: + return False text.mark_set("insert", first) text.undo_block_start() if m.group(): diff --git a/lib-python/2.7/idlelib/config-extensions.def b/lib-python/2.7/idlelib/config-extensions.def --- a/lib-python/2.7/idlelib/config-extensions.def +++ b/lib-python/2.7/idlelib/config-extensions.def @@ -46,6 +46,8 @@ [ScriptBinding] enable=1 +enable_shell=0 +enable_editor=1 [ScriptBinding_cfgBindings] run-module= check-module= diff --git a/lib-python/2.7/idlelib/configDialog.py b/lib-python/2.7/idlelib/configDialog.py --- a/lib-python/2.7/idlelib/configDialog.py +++ b/lib-python/2.7/idlelib/configDialog.py @@ -183,7 +183,7 @@ text=' Highlighting Theme ') #frameCustom self.textHighlightSample=Text(frameCustom,relief=SOLID,borderwidth=1, - font=('courier',12,''),cursor='hand2',width=21,height=10, + font=('courier',12,''),cursor='hand2',width=21,height=11, takefocus=FALSE,highlightthickness=0,wrap=NONE) text=self.textHighlightSample text.bind('',lambda e: 'break') @@ -832,8 +832,9 @@ fontWeight=tkFont.BOLD else: fontWeight=tkFont.NORMAL - self.editFont.config(size=self.fontSize.get(), - weight=fontWeight,family=fontName) + newFont = (fontName, self.fontSize.get(), fontWeight) + self.labelFontSample.config(font=newFont) + self.textHighlightSample.configure(font=newFont) def SetHighlightTarget(self): if self.highlightTarget.get()=='Cursor': #bg not possible @@ -946,7 +947,7 @@ self.listFontName.select_anchor(currentFontIndex) ##font size dropdown fontSize=idleConf.GetOption('main','EditorWindow','font-size', - default='10') + type='int', default='10') self.optMenuFontSize.SetMenu(('7','8','9','10','11','12','13','14', '16','18','20','22'),fontSize ) ##fontWeight @@ -1032,10 +1033,13 @@ self.autoSave.set(idleConf.GetOption('main', 'General', 'autosave', default=0, type='bool')) #initial window size - self.winWidth.set(idleConf.GetOption('main','EditorWindow','width')) - self.winHeight.set(idleConf.GetOption('main','EditorWindow','height')) + self.winWidth.set(idleConf.GetOption('main','EditorWindow','width', + type='int')) + self.winHeight.set(idleConf.GetOption('main','EditorWindow','height', + type='int')) #initial paragraph reformat size - self.paraWidth.set(idleConf.GetOption('main','FormatParagraph','paragraph')) + self.paraWidth.set(idleConf.GetOption('main','FormatParagraph','paragraph', + type='int')) # default source encoding self.encoding.set(idleConf.GetOption('main', 'EditorWindow', 'encoding', default='none')) diff --git a/lib-python/2.7/idlelib/configHandler.py b/lib-python/2.7/idlelib/configHandler.py --- a/lib-python/2.7/idlelib/configHandler.py +++ b/lib-python/2.7/idlelib/configHandler.py @@ -237,24 +237,39 @@ printed to stderr. """ - if self.userCfg[configType].has_option(section,option): - return self.userCfg[configType].Get(section, option, - type=type, raw=raw) - elif self.defaultCfg[configType].has_option(section,option): - return self.defaultCfg[configType].Get(section, option, - type=type, raw=raw) - else: #returning default, print warning - if warn_on_default: - warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n' - ' problem retrieving configuration option %r\n' - ' from section %r.\n' - ' returning default value: %r\n' % - (option, section, default)) - try: - sys.stderr.write(warning) - except IOError: - pass - return default + try: + if self.userCfg[configType].has_option(section,option): + return self.userCfg[configType].Get(section, option, + type=type, raw=raw) + except ValueError: + warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n' + ' invalid %r value for configuration option %r\n' + ' from section %r: %r\n' % + (type, option, section, + self.userCfg[configType].Get(section, option, + raw=raw))) + try: + sys.stderr.write(warning) + except IOError: + pass + try: + if self.defaultCfg[configType].has_option(section,option): + return self.defaultCfg[configType].Get(section, option, + type=type, raw=raw) + except ValueError: + pass + #returning default, print warning + if warn_on_default: + warning = ('\n Warning: configHandler.py - IdleConf.GetOption -\n' + ' problem retrieving configuration option %r\n' + ' from section %r.\n' + ' returning default value: %r\n' % + (option, section, default)) + try: + sys.stderr.write(warning) + except IOError: + pass + return default def SetOption(self, configType, section, option, value): """In user's config file, set section's option to value. @@ -595,7 +610,7 @@ '<>': [''], '<>': [''], '<>': [''], - '<>': [' '], + '<>': ['', ''], '<>': [''], '<>': [''], '<>': [''], diff --git a/lib-python/2.7/idlelib/help.txt b/lib-python/2.7/idlelib/help.txt --- a/lib-python/2.7/idlelib/help.txt +++ b/lib-python/2.7/idlelib/help.txt @@ -80,7 +80,7 @@ Debug Menu (only in Shell window): Go to File/Line -- look around the insert point for a filename - and linenumber, open the file, and show the line + and line number, open the file, and show the line Debugger (toggle) -- Run commands in the shell under the debugger Stack Viewer -- Show the stack traceback of the last exception Auto-open Stack Viewer (toggle) -- Open stack viewer on traceback @@ -92,7 +92,7 @@ Startup Preferences may be set, and Additional Help Sources can be specified. - On MacOS X this menu is not present, use + On OS X this menu is not present, use menu 'IDLE -> Preferences...' instead. --- Code Context -- Open a pane at the top of the edit window which @@ -120,6 +120,24 @@ --- (Additional Help Sources may be added here) +Edit context menu (Right-click / Control-click on OS X in Edit window): + + Cut -- Copy a selection into system-wide clipboard, + then delete the selection + Copy -- Copy selection into system-wide clipboard + Paste -- Insert system-wide clipboard into window + Set Breakpoint -- Sets a breakpoint (when debugger open) + Clear Breakpoint -- Clears the breakpoint on that line + +Shell context menu (Right-click / Control-click on OS X in Shell window): + + Cut -- Copy a selection into system-wide clipboard, + then delete the selection + Copy -- Copy selection into system-wide clipboard + Paste -- Insert system-wide clipboard into window + --- + Go to file/line -- Same as in Debug menu + ** TIPS ** ========== @@ -222,7 +240,7 @@ Alt-p retrieves previous command matching what you have typed. Alt-n retrieves next. - (These are Control-p, Control-n on the Mac) + (These are Control-p, Control-n on OS X) Return while cursor is on a previous command retrieves that command. Expand word is also useful to reduce typing. diff --git a/lib-python/2.7/idlelib/idlever.py b/lib-python/2.7/idlelib/idlever.py --- a/lib-python/2.7/idlelib/idlever.py +++ b/lib-python/2.7/idlelib/idlever.py @@ -1,1 +1,1 @@ -IDLE_VERSION = "2.7.3rc2" +IDLE_VERSION = "2.7.3" diff --git a/lib-python/2.7/idlelib/macosxSupport.py b/lib-python/2.7/idlelib/macosxSupport.py --- a/lib-python/2.7/idlelib/macosxSupport.py +++ b/lib-python/2.7/idlelib/macosxSupport.py @@ -37,17 +37,21 @@ def tkVersionWarning(root): """ Returns a string warning message if the Tk version in use appears to - be one known to cause problems with IDLE. The Apple Cocoa-based Tk 8.5 - that was shipped with Mac OS X 10.6. + be one known to cause problems with IDLE. + 1. Apple Cocoa-based Tk 8.5.7 shipped with Mac OS X 10.6 is unusable. + 2. Apple Cocoa-based Tk 8.5.9 in OS X 10.7 and 10.8 is better but + can still crash unexpectedly. """ if (runningAsOSXApp() and - ('AppKit' in root.tk.call('winfo', 'server', '.')) and - (root.tk.call('info', 'patchlevel') == '8.5.7') ): - return (r"WARNING: The version of Tcl/Tk (8.5.7) in use may" + ('AppKit' in root.tk.call('winfo', 'server', '.')) ): + patchlevel = root.tk.call('info', 'patchlevel') + if patchlevel not in ('8.5.7', '8.5.9'): + return False + return (r"WARNING: The version of Tcl/Tk ({0}) in use may" r" be unstable.\n" r"Visit http://www.python.org/download/mac/tcltk/" - r" for current information.") + r" for current information.".format(patchlevel)) else: return False diff --git a/lib-python/2.7/idlelib/run.py b/lib-python/2.7/idlelib/run.py --- a/lib-python/2.7/idlelib/run.py +++ b/lib-python/2.7/idlelib/run.py @@ -1,4 +1,5 @@ import sys +import io import linecache import time import socket @@ -14,6 +15,8 @@ from idlelib import RemoteObjectBrowser from idlelib import StackViewer from idlelib import rpc +from idlelib import PyShell +from idlelib import IOBinding import __main__ @@ -248,19 +251,19 @@ quitting = True thread.interrupt_main() - class MyHandler(rpc.RPCHandler): def handle(self): """Override base method""" executive = Executive(self) self.register("exec", executive) - sys.stdin = self.console = self.get_remote_proxy("stdin") - sys.stdout = self.get_remote_proxy("stdout") - sys.stderr = self.get_remote_proxy("stderr") - from idlelib import IOBinding - sys.stdin.encoding = sys.stdout.encoding = \ - sys.stderr.encoding = IOBinding.encoding + self.console = self.get_remote_proxy("console") + sys.stdin = PyShell.PseudoInputFile(self.console, "stdin", + IOBinding.encoding) + sys.stdout = PyShell.PseudoOutputFile(self.console, "stdout", + IOBinding.encoding) + sys.stderr = PyShell.PseudoOutputFile(self.console, "stderr", + IOBinding.encoding) self.interp = self.get_remote_proxy("interp") rpc.RPCHandler.getresponse(self, myseq=None, wait=0.05) @@ -298,11 +301,14 @@ exec code in self.locals finally: interruptable = False + except SystemExit: + # Scripts that raise SystemExit should just + # return to the interactive prompt + pass except: self.usr_exc_info = sys.exc_info() if quitting: exit() - # even print a user code SystemExit exception, continue print_exception() jit = self.rpchandler.console.getvar("<>") if jit: diff --git a/lib-python/2.7/io.py b/lib-python/2.7/io.py --- a/lib-python/2.7/io.py +++ b/lib-python/2.7/io.py @@ -4,7 +4,7 @@ At the top of the I/O hierarchy is the abstract base class IOBase. It defines the basic interface to a stream. Note, however, that there is no separation between reading and writing to streams; implementations are -allowed to throw an IOError if they do not support a given operation. +allowed to raise an IOError if they do not support a given operation. Extending IOBase is RawIOBase which deals simply with the reading and writing of raw bytes to a stream. FileIO subclasses RawIOBase to provide @@ -34,15 +34,6 @@ """ # New I/O library conforming to PEP 3116. -# XXX edge cases when switching between reading/writing -# XXX need to support 1 meaning line-buffered -# XXX whenever an argument is None, use the default value -# XXX read/write ops should check readable/writable -# XXX buffered readinto should work with arbitrary buffer objects -# XXX use incremental encoder for text output, at least for UTF-16 and UTF-8-SIG -# XXX check writable, readable and seekable in appropriate places - - __author__ = ("Guido van Rossum , " "Mike Verdone , " "Mark Russell , " diff --git a/lib-python/2.7/json/__init__.py b/lib-python/2.7/json/__init__.py --- a/lib-python/2.7/json/__init__.py +++ b/lib-python/2.7/json/__init__.py @@ -37,8 +37,8 @@ Pretty printing:: >>> import json - >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4) - >>> print '\n'.join([l.rstrip() for l in s.splitlines()]) + >>> print json.dumps({'4': 5, '6': 7}, sort_keys=True, + ... indent=4, separators=(',', ': ')) { "4": 5, "6": 7 @@ -95,7 +95,7 @@ "json": "obj" } $ echo '{ 1.2:3.4}' | python -m json.tool - Expecting property name: line 1 column 2 (char 2) + Expecting property name enclosed in double quotes: line 1 column 3 (char 2) """ __version__ = '2.0.9' __all__ = [ @@ -121,7 +121,7 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', default=None, **kw): + encoding='utf-8', default=None, sort_keys=False, **kw): """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a ``.write()``-supporting file-like object). @@ -129,11 +129,14 @@ (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) will be skipped instead of raising a ``TypeError``. - If ``ensure_ascii`` is false, then the some chunks written to ``fp`` - may be ``unicode`` instances, subject to normal Python ``str`` to - ``unicode`` coercion rules. Unless ``fp.write()`` explicitly - understands ``unicode`` (as in ``codecs.getwriter()``) this is likely - to cause an error. + If ``ensure_ascii`` is true (the default), all non-ASCII characters in the + output are escaped with ``\uXXXX`` sequences, and the result is a ``str`` + instance consisting of ASCII characters only. If ``ensure_ascii`` is + ``False``, some chunks written to ``fp`` may be ``unicode`` instances. + This usually happens because the input contains unicode strings or the + ``encoding`` parameter is used. Unless ``fp.write()`` explicitly + understands ``unicode`` (as in ``codecs.getwriter``) this is likely to + cause an error. If ``check_circular`` is false, then the circular reference check for container types will be skipped and a circular reference will @@ -147,7 +150,9 @@ If ``indent`` is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. ``None`` is the most compact - representation. + representation. Since the default item separator is ``', '``, the + output might include trailing whitespace when ``indent`` is specified. + You can use ``separators=(',', ': ')`` to avoid this. If ``separators`` is an ``(item_separator, dict_separator)`` tuple then it will be used instead of the default ``(', ', ': ')`` separators. @@ -158,6 +163,9 @@ ``default(obj)`` is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. + If *sort_keys* is ``True`` (default: ``False``), then the output of + dictionaries will be sorted by key. + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the ``.default()`` method to serialize additional types), specify it with the ``cls`` kwarg; otherwise ``JSONEncoder`` is used. @@ -167,7 +175,7 @@ if (not skipkeys and ensure_ascii and check_circular and allow_nan and cls is None and indent is None and separators is None and - encoding == 'utf-8' and default is None and not kw): + encoding == 'utf-8' and default is None and not sort_keys and not kw): iterable = _default_encoder.iterencode(obj) else: if cls is None: @@ -175,7 +183,7 @@ iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, indent=indent, separators=separators, encoding=encoding, - default=default, **kw).iterencode(obj) + default=default, sort_keys=sort_keys, **kw).iterencode(obj) # could accelerate with writelines in some versions of Python, at # a debuggability cost for chunk in iterable: @@ -184,16 +192,15 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, - encoding='utf-8', default=None, **kw): + encoding='utf-8', default=None, sort_keys=False, **kw): """Serialize ``obj`` to a JSON formatted ``str``. If ``skipkeys`` is false then ``dict`` keys that are not basic types (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``) will be skipped instead of raising a ``TypeError``. - If ``ensure_ascii`` is false, then the return value will be a - ``unicode`` instance subject to normal Python ``str`` to ``unicode`` - coercion rules instead of being escaped to an ASCII ``str``. + If ``ensure_ascii`` is false, all non-ASCII characters are not escaped, and + the return value may be a ``unicode`` instance. See ``dump`` for details. If ``check_circular`` is false, then the circular reference check for container types will be skipped and a circular reference will @@ -207,7 +214,9 @@ If ``indent`` is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. ``None`` is the most compact - representation. + representation. Since the default item separator is ``', '``, the + output might include trailing whitespace when ``indent`` is specified. + You can use ``separators=(',', ': ')`` to avoid this. If ``separators`` is an ``(item_separator, dict_separator)`` tuple then it will be used instead of the default ``(', ', ': ')`` separators. @@ -218,6 +227,9 @@ ``default(obj)`` is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. + If *sort_keys* is ``True`` (default: ``False``), then the output of + dictionaries will be sorted by key. + To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the ``.default()`` method to serialize additional types), specify it with the ``cls`` kwarg; otherwise ``JSONEncoder`` is used. @@ -227,7 +239,7 @@ if (not skipkeys and ensure_ascii and check_circular and allow_nan and cls is None and indent is None and separators is None and - encoding == 'utf-8' and default is None and not kw): + encoding == 'utf-8' and default is None and not sort_keys and not kw): return _default_encoder.encode(obj) if cls is None: cls = JSONEncoder @@ -235,7 +247,7 @@ skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, indent=indent, separators=separators, encoding=encoding, default=default, - **kw).encode(obj) + sort_keys=sort_keys, **kw).encode(obj) _default_decoder = JSONDecoder(encoding=None, object_hook=None, diff --git a/lib-python/2.7/json/decoder.py b/lib-python/2.7/json/decoder.py --- a/lib-python/2.7/json/decoder.py +++ b/lib-python/2.7/json/decoder.py @@ -27,7 +27,7 @@ def linecol(doc, pos): lineno = doc.count('\n', 0, pos) + 1 if lineno == 1: - colno = pos + colno = pos + 1 else: colno = pos - doc.rindex('\n', 0, pos) return lineno, colno @@ -169,7 +169,8 @@ pairs = object_hook(pairs) return pairs, end + 1 elif nextchar != '"': - raise ValueError(errmsg("Expecting property name", s, end)) + raise ValueError(errmsg( + "Expecting property name enclosed in double quotes", s, end)) end += 1 while True: key, end = scanstring(s, end, encoding, strict) @@ -179,8 +180,7 @@ if s[end:end + 1] != ':': end = _w(s, end).end() if s[end:end + 1] != ':': - raise ValueError(errmsg("Expecting : delimiter", s, end)) - + raise ValueError(errmsg("Expecting ':' delimiter", s, end)) end += 1 try: @@ -209,7 +209,7 @@ if nextchar == '}': break elif nextchar != ',': - raise ValueError(errmsg("Expecting , delimiter", s, end - 1)) + raise ValueError(errmsg("Expecting ',' delimiter", s, end - 1)) try: nextchar = s[end] @@ -224,8 +224,8 @@ end += 1 if nextchar != '"': - raise ValueError(errmsg("Expecting property name", s, end - 1)) - + raise ValueError(errmsg( + "Expecting property name enclosed in double quotes", s, end - 1)) if object_pairs_hook is not None: result = object_pairs_hook(pairs) return result, end @@ -259,8 +259,7 @@ if nextchar == ']': break elif nextchar != ',': - raise ValueError(errmsg("Expecting , delimiter", s, end)) - + raise ValueError(errmsg("Expecting ',' delimiter", s, end)) try: if s[end] in _ws: end += 1 diff --git a/lib-python/2.7/json/encoder.py b/lib-python/2.7/json/encoder.py --- a/lib-python/2.7/json/encoder.py +++ b/lib-python/2.7/json/encoder.py @@ -27,8 +27,7 @@ ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i)) #ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,)) -# Assume this produces an infinity on all machines (probably not guaranteed) -INFINITY = float('1e66666') +INFINITY = float('inf') FLOAT_REPR = repr def encode_basestring(s): @@ -108,9 +107,12 @@ encoding of keys that are not str, int, long, float or None. If skipkeys is True, such items are simply skipped. - If ensure_ascii is true, the output is guaranteed to be str - objects with all incoming unicode characters escaped. If - ensure_ascii is false, the output will be unicode object. + If *ensure_ascii* is true (the default), all non-ASCII + characters in the output are escaped with \uXXXX sequences, + and the results are str instances consisting of ASCII + characters only. If ensure_ascii is False, a result may be a + unicode instance. This usually happens if the input contains + unicode strings or the *encoding* parameter is used. If check_circular is true, then lists, dicts, and custom encoded objects will be checked for circular references during encoding to @@ -129,7 +131,10 @@ If indent is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. - None is the most compact representation. + None is the most compact representation. Since the default + item separator is ', ', the output might include trailing + whitespace when indent is specified. You can use + separators=(',', ': ') to avoid this. If specified, separators should be a (item_separator, key_separator) tuple. The default is (', ', ': '). To get the most compact JSON diff --git a/lib-python/2.7/json/tests/test_decode.py b/lib-python/2.7/json/tests/test_decode.py --- a/lib-python/2.7/json/tests/test_decode.py +++ b/lib-python/2.7/json/tests/test_decode.py @@ -45,6 +45,15 @@ object_hook=lambda x: None), OrderedDict(p)) + def test_extra_data(self): + s = '[1, 2, 3]5' + msg = 'Extra data' + self.assertRaisesRegexp(ValueError, msg, self.loads, s) + + def test_invalid_escape(self): + s = '["abc\\y"]' + msg = 'escape' + self.assertRaisesRegexp(ValueError, msg, self.loads, s) class TestPyDecode(TestDecode, PyTest): pass class TestCDecode(TestDecode, CTest): pass diff --git a/lib-python/2.7/json/tests/test_dump.py b/lib-python/2.7/json/tests/test_dump.py --- a/lib-python/2.7/json/tests/test_dump.py +++ b/lib-python/2.7/json/tests/test_dump.py @@ -19,5 +19,14 @@ {2: 3.0, 4.0: 5L, False: 1, 6L: True}, sort_keys=True), '{"false": 1, "2": 3.0, "4.0": 5, "6": true}') + # Issue 16228: Crash on encoding resized list + def test_encode_mutated(self): + a = [object()] * 10 + def crasher(obj): + del a[-1] + self.assertEqual(self.dumps(a, default=crasher), + '[null, null, null, null, null]') + + class TestPyDump(TestDump, PyTest): pass class TestCDump(TestDump, CTest): pass diff --git a/lib-python/2.7/json/tests/test_fail.py b/lib-python/2.7/json/tests/test_fail.py --- a/lib-python/2.7/json/tests/test_fail.py +++ b/lib-python/2.7/json/tests/test_fail.py @@ -1,13 +1,13 @@ from json.tests import PyTest, CTest -# Fri Dec 30 18:57:26 2005 +# 2007-10-05 JSONDOCS = [ # http://json.org/JSON_checker/test/fail1.json '"A JSON payload should be an object or array, not a string."', # http://json.org/JSON_checker/test/fail2.json '["Unclosed array"', # http://json.org/JSON_checker/test/fail3.json - '{unquoted_key: "keys must be quoted}', + '{unquoted_key: "keys must be quoted"}', # http://json.org/JSON_checker/test/fail4.json '["extra comma",]', # http://json.org/JSON_checker/test/fail5.json @@ -33,7 +33,7 @@ # http://json.org/JSON_checker/test/fail15.json '["Illegal backslash escape: \\x15"]', # http://json.org/JSON_checker/test/fail16.json - '["Illegal backslash escape: \\\'"]', + '[\\naked]', # http://json.org/JSON_checker/test/fail17.json '["Illegal backslash escape: \\017"]', # http://json.org/JSON_checker/test/fail18.json @@ -50,6 +50,24 @@ '["Bad value", truth]', # http://json.org/JSON_checker/test/fail24.json "['single quote']", + # http://json.org/JSON_checker/test/fail25.json + '["\ttab\tcharacter\tin\tstring\t"]', + # http://json.org/JSON_checker/test/fail26.json + '["tab\\ character\\ in\\ string\\ "]', + # http://json.org/JSON_checker/test/fail27.json + '["line\nbreak"]', + # http://json.org/JSON_checker/test/fail28.json + '["line\\\nbreak"]', + # http://json.org/JSON_checker/test/fail29.json + '[0e]', + # http://json.org/JSON_checker/test/fail30.json + '[0e+]', + # http://json.org/JSON_checker/test/fail31.json + '[0e+-1]', + # http://json.org/JSON_checker/test/fail32.json + '{"Comma instead if closing brace": true,', + # http://json.org/JSON_checker/test/fail33.json + '["mismatch"}', # http://code.google.com/p/simplejson/issues/detail?id=3 u'["A\u001FZ control characters in string"]', ] diff --git a/lib-python/2.7/json/tests/test_float.py b/lib-python/2.7/json/tests/test_float.py --- a/lib-python/2.7/json/tests/test_float.py +++ b/lib-python/2.7/json/tests/test_float.py @@ -17,6 +17,21 @@ self.assertEqual(self.loads(self.dumps(num)), num) self.assertEqual(self.loads(unicode(self.dumps(num))), num) + def test_out_of_range(self): + self.assertEqual(self.loads('[23456789012E666]'), [float('inf')]) + self.assertEqual(self.loads('[-23456789012E666]'), [float('-inf')]) + + def test_allow_nan(self): + for val in (float('inf'), float('-inf'), float('nan')): + out = self.dumps([val]) + if val == val: # inf + self.assertEqual(self.loads(out), [val]) + else: # nan + res = self.loads(out) + self.assertEqual(len(res), 1) + self.assertNotEqual(res[0], res[0]) + self.assertRaises(ValueError, self.dumps, [val], allow_nan=False) + class TestPyFloat(TestFloat, PyTest): pass class TestCFloat(TestFloat, CTest): pass diff --git a/lib-python/2.7/json/tests/test_pass1.py b/lib-python/2.7/json/tests/test_pass1.py --- a/lib-python/2.7/json/tests/test_pass1.py +++ b/lib-python/2.7/json/tests/test_pass1.py @@ -17,7 +17,7 @@ "real": -9876.543210, "e": 0.123456789e-12, "E": 1.234567890E+34, - "": 23456789012E666, + "": 23456789012E66, "zero": 0, "one": 1, "space": " ", @@ -28,6 +28,7 @@ "alpha": "abcdefghijklmnopqrstuvwyz", "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", "digit": "0123456789", + "0123456789": "digit", "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", "true": true, @@ -43,8 +44,7 @@ , -4 , 5 , 6 ,7 ], - "compact": [1,2,3,4,5,6,7], +4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", "quotes": "" \u0022 %22 0x22 034 "", "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" @@ -55,9 +55,11 @@ 99.44 , -1066 - - +1066, +1e1, +0.1e1, +1e-1, +1e00,2e+00,2e-00 ,"rosebud"] ''' @@ -67,12 +69,6 @@ res = self.loads(JSON) out = self.dumps(res) self.assertEqual(res, self.loads(out)) - try: - self.dumps(res, allow_nan=False) - except ValueError: - pass - else: - self.fail("23456789012E666 should be out of range") class TestPyPass1(TestPass1, PyTest): pass diff --git a/lib-python/2.7/json/tests/test_tool.py b/lib-python/2.7/json/tests/test_tool.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/json/tests/test_tool.py @@ -0,0 +1,69 @@ +import os +import sys +import textwrap +import unittest +import subprocess +from test import test_support +from test.script_helper import assert_python_ok + +class TestTool(unittest.TestCase): + data = """ + + [["blorpie"],[ "whoops" ] , [ + ],\t"d-shtaeou",\r"d-nthiouh", + "i-vhbjkhnth", {"nifty":87}, {"morefield" :\tfalse,"field" + :"yes"} ] + """ + + expect = textwrap.dedent("""\ + [ + [ + "blorpie" + ], + [ + "whoops" + ], + [], + "d-shtaeou", + "d-nthiouh", + "i-vhbjkhnth", + { + "nifty": 87 + }, + { + "field": "yes", + "morefield": false + } + ] + """) + + def test_stdin_stdout(self): + proc = subprocess.Popen( + (sys.executable, '-m', 'json.tool'), + stdin=subprocess.PIPE, stdout=subprocess.PIPE) + out, err = proc.communicate(self.data.encode()) + self.assertEqual(out.splitlines(), self.expect.encode().splitlines()) + self.assertEqual(err, None) + + def _create_infile(self): + infile = test_support.TESTFN + with open(infile, "w") as fp: + self.addCleanup(os.remove, infile) + fp.write(self.data) + return infile + + def test_infile_stdout(self): + infile = self._create_infile() + rc, out, err = assert_python_ok('-m', 'json.tool', infile) + self.assertEqual(out.splitlines(), self.expect.encode().splitlines()) + self.assertEqual(err, b'') + + def test_infile_outfile(self): + infile = self._create_infile() + outfile = test_support.TESTFN + '.out' + rc, out, err = assert_python_ok('-m', 'json.tool', infile, outfile) + self.addCleanup(os.remove, outfile) + with open(outfile, "r") as fp: + self.assertEqual(fp.read(), self.expect) + self.assertEqual(out, b'') + self.assertEqual(err, b'') diff --git a/lib-python/2.7/json/tool.py b/lib-python/2.7/json/tool.py --- a/lib-python/2.7/json/tool.py +++ b/lib-python/2.7/json/tool.py @@ -7,7 +7,7 @@ "json": "obj" } $ echo '{ 1.2:3.4}' | python -m json.tool - Expecting property name: line 1 column 2 (char 2) + Expecting property name enclosed in double quotes: line 1 column 3 (char 2) """ import sys @@ -25,12 +25,15 @@ outfile = open(sys.argv[2], 'wb') else: raise SystemExit(sys.argv[0] + " [infile [outfile]]") - try: - obj = json.load(infile) - except ValueError, e: - raise SystemExit(e) - json.dump(obj, outfile, sort_keys=True, indent=4) - outfile.write('\n') + with infile: + try: + obj = json.load(infile) + except ValueError, e: + raise SystemExit(e) + with outfile: + json.dump(obj, outfile, sort_keys=True, + indent=4, separators=(',', ': ')) + outfile.write('\n') if __name__ == '__main__': diff --git a/lib-python/2.7/lib-tk/Tkinter.py b/lib-python/2.7/lib-tk/Tkinter.py --- a/lib-python/2.7/lib-tk/Tkinter.py +++ b/lib-python/2.7/lib-tk/Tkinter.py @@ -41,6 +41,7 @@ TclError = _tkinter.TclError from types import * from Tkconstants import * +import re wantobjects = 1 @@ -58,6 +59,37 @@ except AttributeError: _tkinter.deletefilehandler = None +_magic_re = re.compile(r'([\\{}])') +_space_re = re.compile(r'([\s])') + +def _join(value): + """Internal function.""" + return ' '.join(map(_stringify, value)) + +def _stringify(value): + """Internal function.""" + if isinstance(value, (list, tuple)): + if len(value) == 1: + value = _stringify(value[0]) + if value[0] == '{': + value = '{%s}' % value + else: + value = '{%s}' % _join(value) + else: + if isinstance(value, basestring): + value = unicode(value) + else: + value = str(value) + if not value: + value = '{}' + elif _magic_re.search(value): + # add '\' before special characters and spaces + value = _magic_re.sub(r'\\\1', value) + value = _space_re.sub(r'\\\1', value) + elif value[0] == '"' or _space_re.search(value): + value = '{%s}' % value + return value + def _flatten(tuple): """Internal function.""" res = () @@ -154,8 +186,12 @@ """Internal function.""" pass -def _exit(code='0'): - """Internal function. Calling it will throw the exception SystemExit.""" +def _exit(code=0): + """Internal function. Calling it will raise the exception SystemExit.""" + try: + code = int(code) + except ValueError: + pass raise SystemExit, code _varnum = 0 @@ -534,12 +570,19 @@ The type keyword specifies the form in which the data is to be returned and should be an atom name such as STRING - or FILE_NAME. Type defaults to STRING. + or FILE_NAME. Type defaults to STRING, except on X11, where the default + is to try UTF8_STRING and fall back to STRING. This command is equivalent to: selection_get(CLIPBOARD) """ + if 'type' not in kw and self._windowingsystem == 'x11': + try: + kw['type'] = 'UTF8_STRING' + return self.tk.call(('clipboard', 'get') + self._options(kw)) + except TclError: + del kw['type'] return self.tk.call(('clipboard', 'get') + self._options(kw)) def clipboard_clear(self, **kw): @@ -621,8 +664,16 @@ A keyword parameter selection specifies the name of the selection and defaults to PRIMARY. A keyword parameter displayof specifies a widget on the display - to use.""" + to use. A keyword parameter type specifies the form of data to be + fetched, defaulting to STRING except on X11, where UTF8_STRING is tried + before STRING.""" if 'displayof' not in kw: kw['displayof'] = self._w + if 'type' not in kw and self._windowingsystem == 'x11': + try: + kw['type'] = 'UTF8_STRING' + return self.tk.call(('selection', 'get') + self._options(kw)) + except TclError: + del kw['type'] return self.tk.call(('selection', 'get') + self._options(kw)) def selection_handle(self, command, **kw): """Specify a function COMMAND to call if the X @@ -1037,6 +1088,15 @@ if displayof is None: return ('-displayof', self._w) return () + @property + def _windowingsystem(self): + """Internal function.""" + try: + return self._root()._windowingsystem_cached + except AttributeError: + ws = self._root()._windowingsystem_cached = \ + self.tk.call('tk', 'windowingsystem') + return ws def _options(self, cnf, kw = None): """Internal function.""" if kw: @@ -1058,7 +1118,7 @@ nv.append('%d' % item) else: # format it to proper Tcl code if it contains space - nv.append(('{%s}' if ' ' in item else '%s') % item) + nv.append(_stringify(item)) else: v = ' '.join(nv) res = res + ('-'+k, v) @@ -1685,7 +1745,9 @@ self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use) if useTk: self._loadtk() - self.readprofile(baseName, className) + if not sys.flags.ignore_environment: + # Issue #16248: Honor the -E flag to avoid code injection. + self.readprofile(baseName, className) def loadtk(self): if not self._tkloaded: self.tk.loadtk() diff --git a/lib-python/2.7/lib-tk/test/test_ttk/test_functions.py b/lib-python/2.7/lib-tk/test/test_ttk/test_functions.py --- a/lib-python/2.7/lib-tk/test/test_ttk/test_functions.py +++ b/lib-python/2.7/lib-tk/test/test_ttk/test_functions.py @@ -50,13 +50,17 @@ ttk._format_optdict({'test': {'left': 'as is'}}), {'-test': {'left': 'as is'}}) - # check script formatting and untouched value(s) + # check script formatting check_against( ttk._format_optdict( - {'test': [1, -1, '', '2m', 0], 'nochange1': 3, - 'nochange2': 'abc def'}, script=True), - {'-test': '{1 -1 {} 2m 0}', '-nochange1': 3, - '-nochange2': 'abc def' }) + {'test': [1, -1, '', '2m', 0], 'test2': 3, + 'test3': '', 'test4': 'abc def', + 'test5': '"abc"', 'test6': '{}', + 'test7': '} -spam {'}, script=True), + {'-test': '{1 -1 {} 2m 0}', '-test2': '3', + '-test3': '{}', '-test4': '{abc def}', + '-test5': '{"abc"}', '-test6': r'\{\}', + '-test7': r'\}\ -spam\ \{'}) opts = {u'??????': True, u'??': False} orig_opts = opts.copy() @@ -70,6 +74,32 @@ ttk._format_optdict( {'option': ('one two', 'three')}), {'-option': '{one two} three'}) + check_against( + ttk._format_optdict( + {'option': ('one\ttwo', 'three')}), + {'-option': '{one\ttwo} three'}) + + # passing empty strings inside a tuple/list + check_against( + ttk._format_optdict( + {'option': ('', 'one')}), + {'-option': '{} one'}) + + # passing values with braces inside a tuple/list + check_against( + ttk._format_optdict( + {'option': ('one} {two', 'three')}), + {'-option': r'one\}\ \{two three'}) + + # passing quoted strings inside a tuple/list + check_against( + ttk._format_optdict( + {'option': ('"one"', 'two')}), + {'-option': '{"one"} two'}) + check_against( + ttk._format_optdict( + {'option': ('{one}', 'two')}), + {'-option': r'\{one\} two'}) # ignore an option amount_opts = len(ttk._format_optdict(opts, ignore=(u'??'))) // 2 diff --git a/lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py b/lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py --- a/lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py +++ b/lib-python/2.7/lib-tk/test/test_ttk/test_widgets.py @@ -188,6 +188,14 @@ self.combo.configure(values=[1, '', 2]) self.assertEqual(self.combo['values'], ('1', '', '2')) + # testing values with spaces + self.combo['values'] = ['a b', 'a\tb', 'a\nb'] + self.assertEqual(self.combo['values'], ('a b', 'a\tb', 'a\nb')) + + # testing values with special characters + self.combo['values'] = [r'a\tb', '"a"', '} {'] + self.assertEqual(self.combo['values'], (r'a\tb', '"a"', '} {')) + # out of range self.assertRaises(Tkinter.TclError, self.combo.current, len(self.combo['values'])) diff --git a/lib-python/2.7/lib-tk/tkSimpleDialog.py b/lib-python/2.7/lib-tk/tkSimpleDialog.py --- a/lib-python/2.7/lib-tk/tkSimpleDialog.py +++ b/lib-python/2.7/lib-tk/tkSimpleDialog.py @@ -200,7 +200,7 @@ self.entry = Entry(master, name="entry") self.entry.grid(row=1, padx=5, sticky=W+E) - if self.initialvalue: + if self.initialvalue is not None: self.entry.insert(0, self.initialvalue) self.entry.select_range(0, END) diff --git a/lib-python/2.7/lib-tk/ttk.py b/lib-python/2.7/lib-tk/ttk.py --- a/lib-python/2.7/lib-tk/ttk.py +++ b/lib-python/2.7/lib-tk/ttk.py @@ -26,8 +26,7 @@ "tclobjs_to_py", "setup_master"] import Tkinter - -_flatten = Tkinter._flatten +from Tkinter import _flatten, _join, _stringify # Verify if Tk is new enough to not need the Tile package _REQUIRE_TILE = True if Tkinter.TkVersion < 8.5 else False @@ -47,39 +46,56 @@ master.tk.eval('package require tile') # TclError may be raised here master._tile_loaded = True +def _format_optvalue(value, script=False): + """Internal function.""" + if script: + # if caller passes a Tcl script to tk.call, all the values need to + # be grouped into words (arguments to a command in Tcl dialect) + value = _stringify(value) + elif isinstance(value, (list, tuple)): + value = _join(value) + return value + def _format_optdict(optdict, script=False, ignore=None): """Formats optdict to a tuple to pass it to tk.call. E.g. (script=False): {'foreground': 'blue', 'padding': [1, 2, 3, 4]} returns: ('-foreground', 'blue', '-padding', '1 2 3 4')""" - format = "%s" if not script else "{%s}" opts = [] for opt, value in optdict.iteritems(): - if ignore and opt in ignore: - continue + if not ignore or opt not in ignore: + opts.append("-%s" % opt) + if value is not None: + opts.append(_format_optvalue(value, script)) - if isinstance(value, (list, tuple)): - v = [] - for val in value: - if isinstance(val, basestring): - v.append(unicode(val) if val else '{}') - else: - v.append(str(val)) + return _flatten(opts) - # format v according to the script option, but also check for - # space in any value in v in order to group them correctly - value = format % ' '.join( - ('{%s}' if ' ' in val else '%s') % val for val in v) - - if script and value == '': - value = '{}' # empty string in Python is equivalent to {} in Tcl - - opts.append(("-%s" % opt, value)) - - # Remember: _flatten skips over None - return _flatten(opts) +def _mapdict_values(items): + # each value in mapdict is expected to be a sequence, where each item + # is another sequence containing a state (or several) and a value + # E.g. (script=False): + # [('active', 'selected', 'grey'), ('focus', [1, 2, 3, 4])] + # returns: + # ['active selected', 'grey', 'focus', [1, 2, 3, 4]] + opt_val = [] + for item in items: + state = item[:-1] + val = item[-1] + # hacks for bakward compatibility + state[0] # raise IndexError if empty + if len(state) == 1: + # if it is empty (something that evaluates to False), then + # format it to Tcl code to denote the "normal" state + state = state[0] or '' + else: + # group multiple states + state = ' '.join(state) # raise TypeError if not str + opt_val.append(state) + if val is not None: + opt_val.append(val) + return opt_val def _format_mapdict(mapdict, script=False): """Formats mapdict to pass it to tk.call. @@ -90,32 +106,11 @@ returns: ('-expand', '{active selected} grey focus {1, 2, 3, 4}')""" - # if caller passes a Tcl script to tk.call, all the values need to - # be grouped into words (arguments to a command in Tcl dialect) - format = "%s" if not script else "{%s}" opts = [] for opt, value in mapdict.iteritems(): - - opt_val = [] - # each value in mapdict is expected to be a sequence, where each item - # is another sequence containing a state (or several) and a value - for statespec in value: - state, val = statespec[:-1], statespec[-1] - - if len(state) > 1: # group multiple states - state = "{%s}" % ' '.join(state) - else: # single state - # if it is empty (something that evaluates to False), then - # format it to Tcl code to denote the "normal" state - state = state[0] or '{}' - - if isinstance(val, (list, tuple)): # val needs to be grouped - val = "{%s}" % ' '.join(map(str, val)) - - opt_val.append("%s %s" % (state, val)) - - opts.append(("-%s" % opt, format % ' '.join(opt_val))) + opts.extend(("-%s" % opt, + _format_optvalue(_mapdict_values(value), script))) return _flatten(opts) @@ -129,7 +124,7 @@ iname = args[0] # next args, if any, are statespec/value pairs which is almost # a mapdict, but we just need the value - imagespec = _format_mapdict({None: args[1:]})[1] + imagespec = _join(_mapdict_values(args[1:])) spec = "%s %s" % (iname, imagespec) else: @@ -138,7 +133,7 @@ # themed styles on Windows XP and Vista. # Availability: Tk 8.6, Windows XP and Vista. class_name, part_id = args[:2] - statemap = _format_mapdict({None: args[2:]})[1] + statemap = _join(_mapdict_values(args[2:])) spec = "%s %s %s" % (class_name, part_id, statemap) opts = _format_optdict(kw, script) @@ -148,11 +143,11 @@ # otherwise it will clone {} (empty element) spec = args[0] # theme name if len(args) > 1: # elementfrom specified - opts = (args[1], ) + opts = (_format_optvalue(args[1], script),) if script: spec = '{%s}' % spec - opts = ' '.join(map(str, opts)) + opts = ' '.join(opts) return spec, opts @@ -189,7 +184,7 @@ for layout_elem in layout: elem, opts = layout_elem opts = opts or {} - fopts = ' '.join(map(str, _format_optdict(opts, True, "children"))) + fopts = ' '.join(_format_optdict(opts, True, ("children",))) head = "%s%s%s" % (' ' * indent, elem, (" %s" % fopts) if fopts else '') if "children" in opts: @@ -215,11 +210,11 @@ for name, opts in settings.iteritems(): # will format specific keys according to Tcl code if opts.get('configure'): # format 'configure' - s = ' '.join(map(unicode, _format_optdict(opts['configure'], True))) + s = ' '.join(_format_optdict(opts['configure'], True)) script.append("ttk::style configure %s %s;" % (name, s)) if opts.get('map'): # format 'map' - s = ' '.join(map(unicode, _format_mapdict(opts['map'], True))) + s = ' '.join(_format_mapdict(opts['map'], True)) script.append("ttk::style map %s %s;" % (name, s)) if 'layout' in opts: # format 'layout' which may be empty @@ -706,30 +701,9 @@ exportselection, justify, height, postcommand, state, textvariable, values, width """ - # The "values" option may need special formatting, so leave to - # _format_optdict the responsibility to format it - if "values" in kw: - kw["values"] = _format_optdict({'v': kw["values"]})[1] - Entry.__init__(self, master, "ttk::combobox", **kw) - def __setitem__(self, item, value): - if item == "values": - value = _format_optdict({item: value})[1] - - Entry.__setitem__(self, item, value) - - - def configure(self, cnf=None, **kw): - """Custom Combobox configure, created to properly format the values - option.""" - if "values" in kw: - kw["values"] = _format_optdict({'v': kw["values"]})[1] - - return Entry.configure(self, cnf, **kw) - - def current(self, newindex=None): """If newindex is supplied, sets the combobox value to the element at position newindex in the list of values. Otherwise, @@ -1253,7 +1227,7 @@ def exists(self, item): - """Returns True if the specified item is present in the three, + """Returns True if the specified item is present in the tree, False otherwise.""" return bool(self.tk.call(self._w, "exists", item)) diff --git a/lib-python/2.7/lib2to3/fixer_util.py b/lib-python/2.7/lib2to3/fixer_util.py --- a/lib-python/2.7/lib2to3/fixer_util.py +++ b/lib-python/2.7/lib2to3/fixer_util.py @@ -165,7 +165,7 @@ consuming_calls = set(["sorted", "list", "set", "any", "all", "tuple", "sum", - "min", "max"]) + "min", "max", "enumerate"]) def attr_chain(obj, attr): """Follow an attribute chain. @@ -192,14 +192,14 @@ p1 = """ power< ( 'iter' | 'list' | 'tuple' | 'sorted' | 'set' | 'sum' | - 'any' | 'all' | (any* trailer< '.' 'join' >) ) + 'any' | 'all' | 'enumerate' | (any* trailer< '.' 'join' >) ) trailer< '(' node=any ')' > any* > """ p2 = """ power< - 'sorted' + ( 'sorted' | 'enumerate' ) trailer< '(' arglist ')' > any* > @@ -207,14 +207,14 @@ pats_built = False def in_special_context(node): """ Returns true if node is in an environment where all that is required - of it is being itterable (ie, it doesn't matter if it returns a list - or an itterator). + of it is being iterable (ie, it doesn't matter if it returns a list + or an iterator). See test_map_nochange in test_fixers.py for some examples and tests. """ global p0, p1, p2, pats_built if not pats_built: + p0 = patcomp.compile_pattern(p0) p1 = patcomp.compile_pattern(p1) - p0 = patcomp.compile_pattern(p0) p2 = patcomp.compile_pattern(p2) pats_built = True patterns = [p0, p1, p2] @@ -274,9 +274,9 @@ """Find the top level namespace.""" # Scamper up to the top level namespace while node.type != syms.file_input: - assert node.parent, "Tree is insane! root found before "\ - "file_input node was found." node = node.parent + if not node: + raise ValueError("root found before file_input node was found.") return node def does_tree_import(package, name, node): diff --git a/lib-python/2.7/lib2to3/pgen2/driver.py b/lib-python/2.7/lib2to3/pgen2/driver.py --- a/lib-python/2.7/lib2to3/pgen2/driver.py +++ b/lib-python/2.7/lib2to3/pgen2/driver.py @@ -138,3 +138,20 @@ if not os.path.exists(b): return True return os.path.getmtime(a) >= os.path.getmtime(b) + + +def main(*args): + """Main program, when run as a script: produce grammar pickle files. + + Calls load_grammar for each argument, a path to a grammar text file. + """ + if not args: + args = sys.argv[1:] + logging.basicConfig(level=logging.INFO, stream=sys.stdout, + format='%(message)s') + for gt in args: + load_grammar(gt, save=True, force=True) + return True + +if __name__ == "__main__": + sys.exit(int(not main())) diff --git a/lib-python/2.7/lib2to3/refactor.py b/lib-python/2.7/lib2to3/refactor.py --- a/lib-python/2.7/lib2to3/refactor.py +++ b/lib-python/2.7/lib2to3/refactor.py @@ -445,7 +445,7 @@ try: find_root(node) - except AssertionError: + except ValueError: # this node has been cut off from a # previous transformation ; skip continue diff --git a/lib-python/2.7/lib2to3/tests/test_fixers.py b/lib-python/2.7/lib2to3/tests/test_fixers.py --- a/lib-python/2.7/lib2to3/tests/test_fixers.py +++ b/lib-python/2.7/lib2to3/tests/test_fixers.py @@ -2981,6 +2981,10 @@ self.unchanged(a) a = """sorted(filter(f, 'abc'), key=blah)[0]""" self.unchanged(a) + a = """enumerate(filter(f, 'abc'))""" + self.unchanged(a) + a = """enumerate(filter(f, 'abc'), start=1)""" + self.unchanged(a) a = """for i in filter(f, 'abc'): pass""" self.unchanged(a) a = """[x for x in filter(f, 'abc')]""" @@ -3089,6 +3093,10 @@ self.unchanged(a) a = """sorted(map(f, 'abc'), key=blah)[0]""" self.unchanged(a) + a = """enumerate(map(f, 'abc'))""" + self.unchanged(a) + a = """enumerate(map(f, 'abc'), start=1)""" + self.unchanged(a) a = """for i in map(f, 'abc'): pass""" self.unchanged(a) a = """[x for x in map(f, 'abc')]""" @@ -3152,6 +3160,10 @@ self.unchanged(a) a = """sorted(zip(a, b), key=blah)[0]""" self.unchanged(a) + a = """enumerate(zip(a, b))""" + self.unchanged(a) + a = """enumerate(zip(a, b), start=1)""" + self.unchanged(a) a = """for i in zip(a, b): pass""" self.unchanged(a) a = """[x for x in zip(a, b)]""" diff --git a/lib-python/2.7/locale.py b/lib-python/2.7/locale.py --- a/lib-python/2.7/locale.py +++ b/lib-python/2.7/locale.py @@ -18,6 +18,14 @@ import operator import functools +try: + _unicode = unicode +except NameError: + # If Python is built without Unicode support, the unicode type + # will not exist. Fake one. + class _unicode(object): + pass + # Try importing the _locale module. # # If this fails, fall back on a basic 'C' locale emulation. @@ -353,7 +361,7 @@ """ # Normalize the locale name and extract the encoding - if isinstance(localename, unicode): + if isinstance(localename, _unicode): localename = localename.encode('ascii') fullname = localename.translate(_ascii_lower_map) if ':' in fullname: diff --git a/lib-python/2.7/logging/__init__.py b/lib-python/2.7/logging/__init__.py --- a/lib-python/2.7/logging/__init__.py +++ b/lib-python/2.7/logging/__init__.py @@ -180,7 +180,7 @@ _releaseLock() def _checkLevel(level): - if isinstance(level, int): + if isinstance(level, (int, long)): rv = level elif str(level) == level: if level not in _levelNames: @@ -624,7 +624,8 @@ # This function can be called during module teardown, when globals are # set to None. If _acquireLock is None, assume this is the case and do # nothing. - if _acquireLock is not None: + if (_acquireLock is not None and _handlerList is not None and + _releaseLock is not None): _acquireLock() try: if wr in _handlerList: @@ -1173,11 +1174,12 @@ if self.isEnabledFor(ERROR): self._log(ERROR, msg, args, **kwargs) - def exception(self, msg, *args): + def exception(self, msg, *args, **kwargs): """ Convenience method for logging an ERROR with exception information. """ - self.error(msg, exc_info=1, *args) + kwargs['exc_info'] = 1 + self.error(msg, *args, **kwargs) def critical(self, msg, *args, **kwargs): """ @@ -1250,7 +1252,7 @@ all the handlers of this logger to handle the record. """ if _srcfile: - #IronPython doesn't track Python frames, so findCaller throws an + #IronPython doesn't track Python frames, so findCaller raises an #exception on some versions of IronPython. We trap it here so that #IronPython can use logging. try: @@ -1582,12 +1584,13 @@ basicConfig() root.error(msg, *args, **kwargs) -def exception(msg, *args): +def exception(msg, *args, **kwargs): """ Log a message with severity 'ERROR' on the root logger, with exception information. """ - error(msg, exc_info=1, *args) + kwargs['exc_info'] = 1 + error(msg, *args, **kwargs) def warning(msg, *args, **kwargs): """ diff --git a/lib-python/2.7/logging/handlers.py b/lib-python/2.7/logging/handlers.py --- a/lib-python/2.7/logging/handlers.py +++ b/lib-python/2.7/logging/handlers.py @@ -23,7 +23,7 @@ To use, simply 'import logging.handlers' and log away! """ -import logging, socket, os, cPickle, struct, time, re +import errno, logging, socket, os, cPickle, struct, time, re from stat import ST_DEV, ST_INO, ST_MTIME try: @@ -139,7 +139,6 @@ os.remove(dfn) os.rename(self.baseFilename, dfn) #print "%s -> %s" % (self.baseFilename, dfn) - self.mode = 'w' self.stream = self._open() def shouldRollover(self, record): @@ -354,7 +353,6 @@ for s in self.getFilesToDelete(): os.remove(s) #print "%s -> %s" % (self.baseFilename, dfn) - self.mode = 'w' self.stream = self._open() newRolloverAt = self.computeRollover(currentTime) while newRolloverAt <= currentTime: @@ -392,11 +390,13 @@ """ def __init__(self, filename, mode='a', encoding=None, delay=0): logging.FileHandler.__init__(self, filename, mode, encoding, delay) - if not os.path.exists(self.baseFilename): - self.dev, self.ino = -1, -1 - else: - stat = os.stat(self.baseFilename) - self.dev, self.ino = stat[ST_DEV], stat[ST_INO] + self.dev, self.ino = -1, -1 + self._statstream() + + def _statstream(self): + if self.stream: + sres = os.fstat(self.stream.fileno()) + self.dev, self.ino = sres[ST_DEV], sres[ST_INO] def emit(self, record): """ @@ -406,19 +406,27 @@ has, close the old stream and reopen the file to get the current stream. """ - if not os.path.exists(self.baseFilename): - stat = None - changed = 1 - else: - stat = os.stat(self.baseFilename) - changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino) - if changed and self.stream is not None: - self.stream.flush() - self.stream.close() - self.stream = self._open() - if stat is None: - stat = os.stat(self.baseFilename) - self.dev, self.ino = stat[ST_DEV], stat[ST_INO] + # Reduce the chance of race conditions by stat'ing by path only + # once and then fstat'ing our new fd if we opened a new log stream. + # See issue #14632: Thanks to John Mulligan for the problem report + # and patch. + try: + # stat the file by path, checking for existence + sres = os.stat(self.baseFilename) + except OSError as err: + if err.errno == errno.ENOENT: + sres = None + else: + raise + # compare file system stat with that of our stream file handle + if not sres or sres[ST_DEV] != self.dev or sres[ST_INO] != self.ino: + if self.stream is not None: + # we have an open file handle, clean it up + self.stream.flush() + self.stream.close() + # open a new file handle and get new stat info from that fd + self.stream = self._open() + self._statstream() logging.FileHandler.emit(self, record) class SocketHandler(logging.Handler): @@ -528,9 +536,16 @@ """ ei = record.exc_info if ei: - dummy = self.format(record) # just to get traceback text into record.exc_text + # just to get traceback text into record.exc_text ... + dummy = self.format(record) record.exc_info = None # to avoid Unpickleable error - s = cPickle.dumps(record.__dict__, 1) + # See issue #14436: If msg or args are objects, they may not be + # available on the receiving end. So we convert the msg % args + # to a string, save it as msg and zap the args. + d = dict(record.__dict__) + d['msg'] = record.getMessage() + d['args'] = None + s = cPickle.dumps(d, 1) if ei: record.exc_info = ei # for next handler slen = struct.pack(">L", len(s)) @@ -747,14 +762,12 @@ self.formatter = None def _connect_unixsocket(self, address): - self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) - # syslog may require either DGRAM or STREAM sockets + self.socket = socket.socket(socket.AF_UNIX, self.socktype) try: self.socket.connect(address) except socket.error: self.socket.close() - self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.socket.connect(address) + raise # curious: when talking to the unix-domain '/dev/log' socket, a # zero-terminator seems to be required. this string is placed @@ -814,8 +827,6 @@ # Message is a string. Convert to bytes as required by RFC 5424 if type(msg) is unicode: msg = msg.encode('utf-8') - if codecs: - msg = codecs.BOM_UTF8 + msg msg = prio + msg try: if self.unixsocket: @@ -868,6 +879,7 @@ self.toaddrs = toaddrs self.subject = subject self.secure = secure + self._timeout = 5.0 def getSubject(self, record): """ @@ -890,7 +902,7 @@ port = self.mailport if not port: port = smtplib.SMTP_PORT - smtp = smtplib.SMTP(self.mailhost, port) + smtp = smtplib.SMTP(self.mailhost, port, timeout=self._timeout) msg = self.format(record) msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % ( self.fromaddr, diff --git a/lib-python/2.7/mailbox.py b/lib-python/2.7/mailbox.py --- a/lib-python/2.7/mailbox.py +++ b/lib-python/2.7/mailbox.py @@ -197,6 +197,9 @@ """Flush and close the mailbox.""" raise NotImplementedError('Method must be implemented by subclass') + # Whether each message must end in a newline + _append_newline = False + def _dump_message(self, message, target, mangle_from_=False): # Most files are opened in binary mode to allow predictable seeking. # To get native line endings on disk, the user-friendly \n line endings @@ -207,13 +210,21 @@ gen = email.generator.Generator(buffer, mangle_from_, 0) gen.flatten(message) buffer.seek(0) - target.write(buffer.read().replace('\n', os.linesep)) + data = buffer.read().replace('\n', os.linesep) + target.write(data) + if self._append_newline and not data.endswith(os.linesep): + # Make sure the message ends with a newline + target.write(os.linesep) elif isinstance(message, str): if mangle_from_: message = message.replace('\nFrom ', '\n>From ') message = message.replace('\n', os.linesep) target.write(message) + if self._append_newline and not message.endswith(os.linesep): + # Make sure the message ends with a newline + target.write(os.linesep) elif hasattr(message, 'read'): + lastline = None while True: line = message.readline() if line == '': @@ -222,6 +233,10 @@ line = '>From ' + line[5:] line = line.replace('\n', os.linesep) target.write(line) + lastline = line + if self._append_newline and lastline and not lastline.endswith(os.linesep): + # Make sure the message ends with a newline + target.write(os.linesep) else: raise TypeError('Invalid message type: %s' % type(message)) @@ -561,16 +576,19 @@ self._file = f self._toc = None self._next_key = 0 - self._pending = False # No changes require rewriting the file. + self._pending = False # No changes require rewriting the file. + self._pending_sync = False # No need to sync the file self._locked = False - self._file_length = None # Used to record mailbox size + self._file_length = None # Used to record mailbox size def add(self, message): """Add message and return assigned key.""" self._lookup() self._toc[self._next_key] = self._append_message(message) self._next_key += 1 - self._pending = True + # _append_message appends the message to the mailbox file. We + # don't need a full rewrite + rename, sync is enough. + self._pending_sync = True return self._next_key - 1 def remove(self, key): @@ -616,6 +634,11 @@ def flush(self): """Write any pending changes to disk.""" if not self._pending: + if self._pending_sync: + # Messages have only been added, so syncing the file + # is enough. + _sync_flush(self._file) + self._pending_sync = False return # In order to be writing anything out at all, self._toc must @@ -649,6 +672,7 @@ new_file.write(buffer) new_toc[key] = (new_start, new_file.tell()) self._post_message_hook(new_file) + self._file_length = new_file.tell() except: new_file.close() os.remove(new_file.name) @@ -656,6 +680,9 @@ _sync_close(new_file) # self._file is about to get replaced, so no need to sync. self._file.close() + # Make sure the new file's mode is the same as the old file's + mode = os.stat(self._path).st_mode + os.chmod(new_file.name, mode) try: os.rename(new_file.name, self._path) except OSError, e: @@ -668,6 +695,7 @@ self._file = open(self._path, 'rb+') self._toc = new_toc self._pending = False + self._pending_sync = False if self._locked: _lock_file(self._file, dotlock=False) @@ -704,6 +732,12 @@ """Append message to mailbox and return (start, stop) offsets.""" self._file.seek(0, 2) before = self._file.tell() + if len(self._toc) == 0 and not self._pending: + # This is the first message, and the _pre_mailbox_hook + # hasn't yet been called. If self._pending is True, + # messages have been removed, so _pre_mailbox_hook must + # have been called already. + self._pre_mailbox_hook(self._file) try: self._pre_message_hook(self._file) offsets = self._install_message(message) @@ -778,30 +812,48 @@ _mangle_from_ = True + # All messages must end in a newline character, and + # _post_message_hooks outputs an empty line between messages. + _append_newline = True + def __init__(self, path, factory=None, create=True): """Initialize an mbox mailbox.""" self._message_factory = mboxMessage _mboxMMDF.__init__(self, path, factory, create) - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - if f.tell() != 0: - f.write(os.linesep) + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(os.linesep) def _generate_toc(self): """Generate key-to-(start, stop) table of contents.""" starts, stops = [], [] + last_was_empty = False self._file.seek(0) while True: line_pos = self._file.tell() line = self._file.readline() if line.startswith('From '): if len(stops) < len(starts): + if last_was_empty: + stops.append(line_pos - len(os.linesep)) + else: + # The last line before the "From " line wasn't + # blank, but we consider it a start of a + # message anyway. + stops.append(line_pos) + starts.append(line_pos) + last_was_empty = False + elif not line: + if last_was_empty: stops.append(line_pos - len(os.linesep)) - starts.append(line_pos) - elif line == '': - stops.append(line_pos) + else: + stops.append(line_pos) break + elif line == os.linesep: + last_was_empty = True + else: + last_was_empty = False self._toc = dict(enumerate(zip(starts, stops))) self._next_key = len(self._toc) self._file_length = self._file.tell() @@ -1367,9 +1419,9 @@ line = message.readline() self._file.write(line.replace('\n', os.linesep)) if line == '\n' or line == '': - self._file.write('*** EOOH ***' + os.linesep) if first_pass: first_pass = False + self._file.write('*** EOOH ***' + os.linesep) message.seek(original_pos) else: break diff --git a/lib-python/2.7/mimetypes.py b/lib-python/2.7/mimetypes.py --- a/lib-python/2.7/mimetypes.py +++ b/lib-python/2.7/mimetypes.py @@ -432,11 +432,12 @@ '.hdf' : 'application/x-hdf', '.htm' : 'text/html', '.html' : 'text/html', + '.ico' : 'image/vnd.microsoft.icon', '.ief' : 'image/ief', '.jpe' : 'image/jpeg', '.jpeg' : 'image/jpeg', '.jpg' : 'image/jpeg', - '.js' : 'application/x-javascript', + '.js' : 'application/javascript', '.ksh' : 'text/plain', '.latex' : 'application/x-latex', '.m1v' : 'video/mpeg', diff --git a/lib-python/2.7/multiprocessing/connection.py b/lib-python/2.7/multiprocessing/connection.py --- a/lib-python/2.7/multiprocessing/connection.py +++ b/lib-python/2.7/multiprocessing/connection.py @@ -186,6 +186,8 @@ ''' if duplex: s1, s2 = socket.socketpair() + s1.setblocking(True) + s2.setblocking(True) c1 = _multiprocessing.Connection(os.dup(s1.fileno())) c2 = _multiprocessing.Connection(os.dup(s2.fileno())) s1.close() @@ -198,7 +200,6 @@ return c1, c2 else: - from _multiprocessing import win32 def Pipe(duplex=True): @@ -251,6 +252,7 @@ self._socket = socket.socket(getattr(socket, family)) try: self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self._socket.setblocking(True) self._socket.bind(address) self._socket.listen(backlog) self._address = self._socket.getsockname() @@ -269,6 +271,7 @@ def accept(self): s, self._last_accepted = self._socket.accept() + s.setblocking(True) fd = duplicate(s.fileno()) conn = _multiprocessing.Connection(fd) s.close() @@ -286,6 +289,7 @@ ''' family = address_type(address) s = socket.socket( getattr(socket, family) ) + s.setblocking(True) t = _init_timeout() while 1: @@ -348,7 +352,10 @@ try: win32.ConnectNamedPipe(handle, win32.NULL) except WindowsError, e: - if e.args[0] != win32.ERROR_PIPE_CONNECTED: + # ERROR_NO_DATA can occur if a client has already connected, + # written data and then disconnected -- see Issue 14725. + if e.args[0] not in (win32.ERROR_PIPE_CONNECTED, + win32.ERROR_NO_DATA): raise return _multiprocessing.PipeConnection(handle) diff --git a/lib-python/2.7/multiprocessing/dummy/__init__.py b/lib-python/2.7/multiprocessing/dummy/__init__.py --- a/lib-python/2.7/multiprocessing/dummy/__init__.py +++ b/lib-python/2.7/multiprocessing/dummy/__init__.py @@ -70,7 +70,8 @@ def start(self): assert self._parent is current_process() self._start_called = True - self._parent._children[self] = None + if hasattr(self._parent, '_children'): + self._parent._children[self] = None threading.Thread.start(self) @property diff --git a/lib-python/2.7/multiprocessing/forking.py b/lib-python/2.7/multiprocessing/forking.py --- a/lib-python/2.7/multiprocessing/forking.py +++ b/lib-python/2.7/multiprocessing/forking.py @@ -35,6 +35,7 @@ import os import sys import signal +import errno from multiprocessing import util, process @@ -129,12 +130,17 @@ def poll(self, flag=os.WNOHANG): if self.returncode is None: - try: - pid, sts = os.waitpid(self.pid, flag) - except os.error: - # Child process not yet created. See #1731717 - # e.errno == errno.ECHILD == 10 - return None + while True: + try: + pid, sts = os.waitpid(self.pid, flag) + except os.error as e: + if e.errno == errno.EINTR: + continue + # Child process not yet created. See #1731717 + # e.errno == errno.ECHILD == 10 + return None + else: + break if pid == self.pid: if os.WIFSIGNALED(sts): self.returncode = -os.WTERMSIG(sts) @@ -336,7 +342,7 @@ ''' Returns prefix of command line used for spawning a child process ''' - if process.current_process()._identity==() and is_forking(sys.argv): + if getattr(process.current_process(), '_inheriting', False): raise RuntimeError(''' Attempt to start a new process before the current process has finished its bootstrapping phase. diff --git a/lib-python/2.7/multiprocessing/pool.py b/lib-python/2.7/multiprocessing/pool.py --- a/lib-python/2.7/multiprocessing/pool.py +++ b/lib-python/2.7/multiprocessing/pool.py @@ -68,6 +68,23 @@ # Code run by worker processes # +class MaybeEncodingError(Exception): + """Wraps possible unpickleable errors, so they can be + safely sent through the socket.""" + + def __init__(self, exc, value): + self.exc = repr(exc) + self.value = repr(value) + super(MaybeEncodingError, self).__init__(self.exc, self.value) + + def __str__(self): + return "Error sending result: '%s'. Reason: '%s'" % (self.value, + self.exc) + + def __repr__(self): + return "" % str(self) + + def worker(inqueue, outqueue, initializer=None, initargs=(), maxtasks=None): assert maxtasks is None or (type(maxtasks) == int and maxtasks > 0) put = outqueue.put @@ -96,7 +113,13 @@ result = (True, func(*args, **kwds)) except Exception, e: result = (False, e) - put((job, i, result)) + try: + put((job, i, result)) + except Exception as e: + wrapped = MaybeEncodingError(e, result[1]) + debug("Possible encoding error while sending result: %s" % ( + wrapped)) + put((job, i, (False, wrapped))) completed += 1 debug('worker exiting after %d tasks' % completed) @@ -466,7 +489,8 @@ # We must wait for the worker handler to exit before terminating # workers because we don't want workers to be restarted behind our back. debug('joining worker handler') - worker_handler.join() + if threading.current_thread() is not worker_handler: + worker_handler.join(1e100) # Terminate workers which haven't already finished. if pool and hasattr(pool[0], 'terminate'): @@ -476,10 +500,12 @@ p.terminate() debug('joining task handler') - task_handler.join(1e100) + if threading.current_thread() is not task_handler: + task_handler.join(1e100) debug('joining result handler') - result_handler.join(1e100) + if threading.current_thread() is not result_handler: + result_handler.join(1e100) if pool and hasattr(pool[0], 'terminate'): debug('joining pool workers') @@ -553,6 +579,7 @@ if chunksize <= 0: self._number_left = 0 self._ready = True + del cache[self._job] else: self._number_left = length//chunksize + bool(length % chunksize) diff --git a/lib-python/2.7/multiprocessing/process.py b/lib-python/2.7/multiprocessing/process.py --- a/lib-python/2.7/multiprocessing/process.py +++ b/lib-python/2.7/multiprocessing/process.py @@ -262,12 +262,12 @@ except SystemExit, e: if not e.args: exitcode = 1 - elif type(e.args[0]) is int: + elif isinstance(e.args[0], int): exitcode = e.args[0] else: - sys.stderr.write(e.args[0] + '\n') + sys.stderr.write(str(e.args[0]) + '\n') sys.stderr.flush() - exitcode = 1 + exitcode = 0 if isinstance(e.args[0], str) else 1 except: exitcode = 1 import traceback diff --git a/lib-python/2.7/multiprocessing/util.py b/lib-python/2.7/multiprocessing/util.py --- a/lib-python/2.7/multiprocessing/util.py +++ b/lib-python/2.7/multiprocessing/util.py @@ -247,6 +247,12 @@ Finalizers with highest priority are called first; finalizers with the same priority will be called in reverse order of creation. ''' + if _finalizer_registry is None: + # This function may be called after this module's globals are + # destroyed. See the _exit_function function in this module for more + # notes. + return + if minpriority is None: f = lambda p : p[0][0] is not None else: @@ -278,21 +284,38 @@ _exiting = False -def _exit_function(): +def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers, + active_children=active_children, + current_process=current_process): + # NB: we hold on to references to functions in the arglist due to the + # situation described below, where this function is called after this + # module's globals are destroyed. + global _exiting info('process shutting down') debug('running all "atexit" finalizers with priority >= 0') _run_finalizers(0) - for p in active_children(): - if p._daemonic: - info('calling terminate() for daemon %s', p.name) - p._popen.terminate() + if current_process() is not None: + # NB: we check if the current process is None here because if + # it's None, any call to ``active_children()`` will throw an + # AttributeError (active_children winds up trying to get + # attributes from util._current_process). This happens in a + # variety of shutdown circumstances that are not well-understood + # because module-scope variables are not apparently supposed to + # be destroyed until after this function is called. However, + # they are indeed destroyed before this function is called. See + # issues 9775 and 15881. Also related: 4106, 9205, and 9207. - for p in active_children(): - info('calling join() for process %s', p.name) - p.join() + for p in active_children(): + if p._daemonic: + info('calling terminate() for daemon %s', p.name) + p._popen.terminate() + + for p in active_children(): + info('calling join() for process %s', p.name) + p.join() debug('running the remaining "atexit" finalizers') _run_finalizers() diff --git a/lib-python/2.7/plat-generic/regen b/lib-python/2.7/plat-generic/regen --- a/lib-python/2.7/plat-generic/regen +++ b/lib-python/2.7/plat-generic/regen @@ -1,3 +1,3 @@ #! /bin/sh set -v -python$EXE ../../Tools/scripts/h2py.py -i '(u_long)' /usr/include/netinet/in.h +eval $PYTHON_FOR_BUILD ../../Tools/scripts/h2py.py -i "'(u_long)'" /usr/include/netinet/in.h diff --git a/lib-python/2.7/platform.py b/lib-python/2.7/platform.py --- a/lib-python/2.7/platform.py +++ b/lib-python/2.7/platform.py @@ -673,8 +673,13 @@ release = '7' else: release = '2008ServerR2' + elif min == 2: + if product_type == VER_NT_WORKSTATION: + release = '8' + else: + release = '2012Server' else: - release = 'post2008Server' + release = 'post2012Server' else: if not release: @@ -1020,16 +1025,38 @@ case the command should fail. """ + + # We do the import here to avoid a bootstrap issue. + # See c73b90b6dadd changeset. + # + # [..] + # ranlib libpython2.7.a + # gcc -o python \ + # Modules/python.o \ + # libpython2.7.a -lsocket -lnsl -ldl -lm + # Traceback (most recent call last): + # File "./setup.py", line 8, in + # from platform import machine as platform_machine + # File "[..]/build/Lib/platform.py", line 116, in + # import sys,string,os,re,subprocess + # File "[..]/build/Lib/subprocess.py", line 429, in + # import select + # ImportError: No module named select + + import subprocess + if sys.platform in ('dos','win32','win16','os2'): # XXX Others too ? return default - target = _follow_symlinks(target).replace('"', '\\"') + target = _follow_symlinks(target) try: - f = os.popen('file "%s" 2> %s' % (target, DEV_NULL)) + proc = subprocess.Popen(['file', target], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except (AttributeError,os.error): return default - output = string.strip(f.read()) - rc = f.close() + output = proc.communicate()[0] + rc = proc.wait() if not output or rc: return default else: diff --git a/lib-python/2.7/posixpath.py b/lib-python/2.7/posixpath.py --- a/lib-python/2.7/posixpath.py +++ b/lib-python/2.7/posixpath.py @@ -17,6 +17,14 @@ import warnings from genericpath import * +try: + _unicode = unicode +except NameError: + # If Python is built without Unicode support, the unicode type + # will not exist. Fake one. + class _unicode(object): + pass + __all__ = ["normcase","isabs","join","splitdrive","split","splitext", "basename","dirname","commonprefix","getsize","getmtime", "getatime","getctime","islink","exists","lexists","isdir","isfile", @@ -60,7 +68,8 @@ def join(a, *p): """Join two or more pathname components, inserting '/' as needed. If any component is an absolute path, all previous path components - will be discarded.""" + will be discarded. An empty last part will result in a path that + ends with a separator.""" path = a for b in p: if b.startswith('/'): @@ -267,8 +276,8 @@ except KeyError: return path userhome = pwent.pw_dir - userhome = userhome.rstrip('/') or userhome - return userhome + path[i:] + userhome = userhome.rstrip('/') + return (userhome + path[i:]) or '/' # Expand paths containing shell variable substitutions. @@ -312,7 +321,7 @@ def normpath(path): """Normalize path, eliminating double slashes, etc.""" # Preserve unicode (if path is unicode) - slash, dot = (u'/', u'.') if isinstance(path, unicode) else ('/', '.') + slash, dot = (u'/', u'.') if isinstance(path, _unicode) else ('/', '.') if path == '': return dot initial_slashes = path.startswith('/') @@ -341,7 +350,7 @@ def abspath(path): """Return an absolute path.""" if not isabs(path): - if isinstance(path, unicode): + if isinstance(path, _unicode): cwd = os.getcwdu() else: cwd = os.getcwd() @@ -355,46 +364,53 @@ def realpath(filename): """Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path.""" - if isabs(filename): - bits = ['/'] + filename.split('/')[1:] - else: - bits = [''] + filename.split('/') + path, ok = _joinrealpath('', filename, {}) + return abspath(path) - for i in range(2, len(bits)+1): - component = join(*bits[0:i]) - # Resolve symbolic links. - if islink(component): - resolved = _resolve_link(component) - if resolved is None: - # Infinite loop -- return original component + rest of the path - return abspath(join(*([component] + bits[i:]))) +# Join two paths, normalizing ang eliminating any symbolic links +# encountered in the second path. +def _joinrealpath(path, rest, seen): + if isabs(rest): + rest = rest[1:] + path = sep + + while rest: + name, _, rest = rest.partition(sep) + if not name or name == curdir: + # current dir + continue + if name == pardir: + # parent dir + if path: + path, name = split(path) + if name == pardir: + path = join(path, pardir, pardir) else: - newpath = join(*([resolved] + bits[i:])) - return realpath(newpath) + path = pardir + continue + newpath = join(path, name) + if not islink(newpath): + path = newpath + continue + # Resolve the symbolic link + if newpath in seen: + # Already seen this path + path = seen[newpath] + if path is not None: + # use cached value + continue + # The symlink is not resolved, so we must have a symlink loop. + # Return already resolved part + rest of the path unchanged. + return join(newpath, rest), False + seen[newpath] = None # not resolved symlink + path, ok = _joinrealpath(path, os.readlink(newpath), seen) + if not ok: + return join(path, rest), False + seen[newpath] = path # resolved symlink - return abspath(filename) + return path, True -def _resolve_link(path): - """Internal helper function. Takes a path and follows symlinks - until we either arrive at something that isn't a symlink, or - encounter a path we've seen before (meaning that there's a loop). - """ - paths_seen = set() - while islink(path): - if path in paths_seen: - # Already seen this path, so we must have a symlink loop - return None - paths_seen.add(path) - # Resolve where the link points to - resolved = os.readlink(path) - if not isabs(resolved): - dir = dirname(path) - path = normpath(join(dir, resolved)) - else: - path = normpath(resolved) - return path - supports_unicode_filenames = (sys.platform == 'darwin') def relpath(path, start=curdir): diff --git a/lib-python/2.7/pstats.py b/lib-python/2.7/pstats.py --- a/lib-python/2.7/pstats.py +++ b/lib-python/2.7/pstats.py @@ -120,8 +120,8 @@ self.stats = arg.stats arg.stats = {} if not self.stats: - raise TypeError, "Cannot create or construct a %r object from '%r''" % ( - self.__class__, arg) + raise TypeError("Cannot create or construct a %r object from %r" + % (self.__class__, arg)) return def get_top_level_stats(self): @@ -172,15 +172,19 @@ # along with some printable description sort_arg_dict_default = { "calls" : (((1,-1), ), "call count"), + "ncalls" : (((1,-1), ), "call count"), + "cumtime" : (((3,-1), ), "cumulative time"), "cumulative": (((3,-1), ), "cumulative time"), "file" : (((4, 1), ), "file name"), + "filename" : (((4, 1), ), "file name"), "line" : (((5, 1), ), "line number"), "module" : (((4, 1), ), "file name"), "name" : (((6, 1), ), "function name"), "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"), - "pcalls" : (((0,-1), ), "call count"), + "pcalls" : (((0,-1), ), "primitive call count"), "stdname" : (((7, 1), ), "standard name"), "time" : (((2,-1), ), "internal time"), + "tottime" : (((2,-1), ), "internal time"), } def get_sort_arg_defs(self): diff --git a/lib-python/2.7/py_compile.py b/lib-python/2.7/py_compile.py --- a/lib-python/2.7/py_compile.py +++ b/lib-python/2.7/py_compile.py @@ -112,7 +112,7 @@ try: codeobject = __builtin__.compile(codestring, dfile or file,'exec') except Exception,err: - py_exc = PyCompileError(err.__class__,err.args,dfile or file) + py_exc = PyCompileError(err.__class__, err, dfile or file) if doraise: raise py_exc else: diff --git a/lib-python/2.7/pyclbr.py b/lib-python/2.7/pyclbr.py --- a/lib-python/2.7/pyclbr.py +++ b/lib-python/2.7/pyclbr.py @@ -128,6 +128,8 @@ parent = _readmodule(package, path, inpackage) if inpackage is not None: package = "%s.%s" % (inpackage, package) + if not '__path__' in parent: + raise ImportError('No package named {}'.format(package)) return _readmodule(submodule, parent['__path__'], package) # Search the path for the module diff --git a/lib-python/2.7/pydoc.py b/lib-python/2.7/pydoc.py --- a/lib-python/2.7/pydoc.py +++ b/lib-python/2.7/pydoc.py @@ -1498,7 +1498,8 @@ raise ImportError, 'no Python documentation found for %r' % thing return object, thing else: - return thing, getattr(thing, '__name__', None) + name = getattr(thing, '__name__', None) + return thing, name if isinstance(name, str) else None def render_doc(thing, title='Python Library Documentation: %s', forceload=0): """Render text documentation, given an object or a path to an object.""" @@ -1799,7 +1800,7 @@ Welcome to Python %s! This is the online help utility. If this is your first time using Python, you should definitely check out -the tutorial on the Internet at http://docs.python.org/tutorial/. +the tutorial on the Internet at http://docs.python.org/%s/tutorial/. Enter the name of any module, keyword, or topic to get help on writing Python programs and using Python modules. To quit this help utility and @@ -1809,7 +1810,7 @@ "keywords", or "topics". Each module also comes with a one-line summary of what it does; to list the modules whose summaries contain a given word such as "spam", type "modules spam". -''' % sys.version[:3]) +''' % tuple([sys.version[:3]]*2)) def list(self, items, columns=4, width=80): items = items[:] diff --git a/lib-python/2.7/random.py b/lib-python/2.7/random.py --- a/lib-python/2.7/random.py +++ b/lib-python/2.7/random.py @@ -457,27 +457,25 @@ if kappa <= 1e-6: return TWOPI * random() - a = 1.0 + _sqrt(1.0 + 4.0 * kappa * kappa) - b = (a - _sqrt(2.0 * a))/(2.0 * kappa) - r = (1.0 + b * b)/(2.0 * b) + s = 0.5 / kappa + r = s + _sqrt(1.0 + s * s) while 1: u1 = random() + z = _cos(_pi * u1) - z = _cos(_pi * u1) - f = (1.0 + r * z)/(r + z) - c = kappa * (r - f) - + d = z / (r + z) u2 = random() - - if u2 < c * (2.0 - c) or u2 <= c * _exp(1.0 - c): + if u2 < 1.0 - d * d or u2 <= (1.0 - d) * _exp(d): break + q = 1.0 / r + f = (q + z) / (1.0 + q * z) u3 = random() if u3 > 0.5: - theta = (mu % TWOPI) + _acos(f) + theta = (mu + _acos(f)) % TWOPI else: - theta = (mu % TWOPI) - _acos(f) + theta = (mu - _acos(f)) % TWOPI return theta diff --git a/lib-python/2.7/rfc822.py b/lib-python/2.7/rfc822.py --- a/lib-python/2.7/rfc822.py +++ b/lib-python/2.7/rfc822.py @@ -212,7 +212,7 @@ You may override this method if your application wants to bend the rules, e.g. to strip trailing whitespace, or to recognize MH template separators ('--------'). For convenience (e.g. for code reading from - sockets) a line consisting of \r\n also matches. + sockets) a line consisting of \\r\\n also matches. """ return line in _blanklines diff --git a/lib-python/2.7/rlcompleter.py b/lib-python/2.7/rlcompleter.py --- a/lib-python/2.7/rlcompleter.py +++ b/lib-python/2.7/rlcompleter.py @@ -1,13 +1,11 @@ -"""Word completion for GNU readline 2.0. +"""Word completion for GNU readline. -This requires the latest extension to the readline module. The completer -completes keywords, built-ins and globals in a selectable namespace (which -defaults to __main__); when completing NAME.NAME..., it evaluates (!) the -expression up to the last dot and completes its attributes. +The completer completes keywords, built-ins and globals in a selectable +namespace (which defaults to __main__); when completing NAME.NAME..., it +evaluates (!) the expression up to the last dot and completes its attributes. -It's very cool to do "import sys" type "sys.", hit the -completion key (twice), and see the list of names defined by the -sys module! +It's very cool to do "import sys" type "sys.", hit the completion key (twice), +and see the list of names defined by the sys module! Tip: to use the tab key as the completion key, call @@ -15,18 +13,16 @@ Notes: -- Exceptions raised by the completer function are *ignored* (and -generally cause the completion to fail). This is a feature -- since -readline sets the tty device in raw (or cbreak) mode, printing a -traceback wouldn't work well without some complicated hoopla to save, -reset and restore the tty state. +- Exceptions raised by the completer function are *ignored* (and generally cause + the completion to fail). This is a feature -- since readline sets the tty + device in raw (or cbreak) mode, printing a traceback wouldn't work well + without some complicated hoopla to save, reset and restore the tty state. -- The evaluation of the NAME.NAME... form may cause arbitrary -application defined code to be executed if an object with a -__getattr__ hook is found. Since it is the responsibility of the -application (or the user) to enable this feature, I consider this an -acceptable risk. More complicated expressions (e.g. function calls or -indexing operations) are *not* evaluated. +- The evaluation of the NAME.NAME... form may cause arbitrary application + defined code to be executed if an object with a __getattr__ hook is found. + Since it is the responsibility of the application (or the user) to enable this + feature, I consider this an acceptable risk. More complicated expressions + (e.g. function calls or indexing operations) are *not* evaluated. - GNU readline is also used by the built-in functions input() and raw_input(), and thus these also benefit/suffer from the completer @@ -35,7 +31,7 @@ its input. - When the original stdin is not a tty device, GNU readline is never -used, and this module (and the readline module) are silently inactive. + used, and this module (and the readline module) are silently inactive. """ diff --git a/lib-python/2.7/runpy.py b/lib-python/2.7/runpy.py --- a/lib-python/2.7/runpy.py +++ b/lib-python/2.7/runpy.py @@ -200,7 +200,7 @@ pass else: # The following check looks a bit odd. The trick is that - # NullImporter throws ImportError if the supplied path is a + # NullImporter raises ImportError if the supplied path is a # *valid* directory entry (and hence able to be handled # by the standard import machinery) try: diff --git a/lib-python/2.7/shutil.py b/lib-python/2.7/shutil.py --- a/lib-python/2.7/shutil.py +++ b/lib-python/2.7/shutil.py @@ -102,8 +102,10 @@ try: os.chflags(dst, st.st_flags) except OSError, why: - if (not hasattr(errno, 'EOPNOTSUPP') or - why.errno != errno.EOPNOTSUPP): + for err in 'EOPNOTSUPP', 'ENOTSUP': + if hasattr(errno, err) and why.errno == getattr(errno, err): + break + else: raise def copy(src, dst): @@ -201,7 +203,7 @@ # Copying file access times may fail on Windows pass else: - errors.extend((src, dst, str(why))) + errors.append((src, dst, str(why))) if errors: raise Error, errors diff --git a/lib-python/2.7/smtplib.py b/lib-python/2.7/smtplib.py --- a/lib-python/2.7/smtplib.py +++ b/lib-python/2.7/smtplib.py @@ -818,13 +818,13 @@ try: self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(host) - except socket.error, msg: + except socket.error: if self.debuglevel > 0: print>>stderr, 'connect fail:', host if self.sock: self.sock.close() self.sock = None - raise socket.error, msg + raise (code, msg) = self.getreply() if self.debuglevel > 0: print>>stderr, "connect:", msg diff --git a/lib-python/2.7/socket.py b/lib-python/2.7/socket.py --- a/lib-python/2.7/socket.py +++ b/lib-python/2.7/socket.py @@ -319,8 +319,8 @@ self._wbuf.append(data) self._wbuf_len += len(data) if (self._wbufsize == 0 or - self._wbufsize == 1 and '\n' in data or - self._wbuf_len >= self._wbufsize): + (self._wbufsize == 1 and '\n' in data) or + (self._wbufsize > 1 and self._wbuf_len >= self._wbufsize)): self.flush() def writelines(self, list): diff --git a/lib-python/2.7/sqlite3/dbapi2.py b/lib-python/2.7/sqlite3/dbapi2.py --- a/lib-python/2.7/sqlite3/dbapi2.py +++ b/lib-python/2.7/sqlite3/dbapi2.py @@ -1,4 +1,4 @@ -#-*- coding: ISO-8859-1 -*- +# -*- coding: iso-8859-1 -*- # pysqlite2/dbapi2.py: the DB-API 2.0 interface # # Copyright (C) 2004-2005 Gerhard H?ring @@ -68,7 +68,7 @@ timepart_full = timepart.split(".") hours, minutes, seconds = map(int, timepart_full[0].split(":")) if len(timepart_full) == 2: - microseconds = int(timepart_full[1]) + microseconds = int('{:0<6.6}'.format(timepart_full[1].decode())) else: microseconds = 0 diff --git a/lib-python/2.7/sqlite3/dump.py b/lib-python/2.7/sqlite3/dump.py --- a/lib-python/2.7/sqlite3/dump.py +++ b/lib-python/2.7/sqlite3/dump.py @@ -25,9 +25,10 @@ FROM "sqlite_master" WHERE "sql" NOT NULL AND "type" == 'table' + ORDER BY "name" """ schema_res = cu.execute(q) - for table_name, type, sql in sorted(schema_res.fetchall()): + for table_name, type, sql in schema_res.fetchall(): if table_name == 'sqlite_sequence': yield('DELETE FROM "sqlite_sequence";') elif table_name == 'sqlite_stat1': @@ -42,7 +43,7 @@ # qtable, # sql.replace("''"))) else: - yield('{0};'.format(sql)) + yield('%s;' % sql) # Build the insert statement for each row of the current table table_name_ident = table_name.replace('"', '""') @@ -53,7 +54,7 @@ ",".join("""'||quote("{0}")||'""".format(col.replace('"', '""')) for col in column_names)) query_res = cu.execute(q) for row in query_res: - yield("{0};".format(row[0])) + yield("%s;" % row[0]) # Now when the type is 'index', 'trigger', or 'view' q = """ @@ -64,6 +65,6 @@ """ schema_res = cu.execute(q) for name, type, sql in schema_res.fetchall(): - yield('{0};'.format(sql)) + yield('%s;' % sql) yield('COMMIT;') diff --git a/lib-python/2.7/sqlite3/test/dump.py b/lib-python/2.7/sqlite3/test/dump.py --- a/lib-python/2.7/sqlite3/test/dump.py +++ b/lib-python/2.7/sqlite3/test/dump.py @@ -29,6 +29,8 @@ , "INSERT INTO \"t1\" VALUES(2,'foo2',30,30);" , + u"INSERT INTO \"t1\" VALUES(3,'f\xc3\xb6',40,10);" + , "CREATE TABLE t2(id integer, t2_i1 integer, " \ "t2_i2 integer, primary key (id)," \ "foreign key(t2_i1) references t1(t1_i1));" @@ -49,6 +51,27 @@ [self.assertEqual(expected_sqls[i], actual_sqls[i]) for i in xrange(len(expected_sqls))] + def CheckUnorderableRow(self): + # iterdump() should be able to cope with unorderable row types (issue #15545) + class UnorderableRow: + def __init__(self, cursor, row): + self.row = row + def __getitem__(self, index): + return self.row[index] + self.cx.row_factory = UnorderableRow + CREATE_ALPHA = """CREATE TABLE "alpha" ("one");""" + CREATE_BETA = """CREATE TABLE "beta" ("two");""" + expected = [ + "BEGIN TRANSACTION;", + CREATE_ALPHA, + CREATE_BETA, + "COMMIT;" + ] + self.cu.execute(CREATE_BETA) + self.cu.execute(CREATE_ALPHA) + got = list(self.cx.iterdump()) + self.assertEqual(expected, got) + def suite(): return unittest.TestSuite(unittest.makeSuite(DumpTests, "Check")) diff --git a/lib-python/2.7/sqlite3/test/hooks.py b/lib-python/2.7/sqlite3/test/hooks.py --- a/lib-python/2.7/sqlite3/test/hooks.py +++ b/lib-python/2.7/sqlite3/test/hooks.py @@ -76,6 +76,25 @@ except sqlite.OperationalError, e: self.assertEqual(e.args[0].lower(), "no such collation sequence: mycoll") + def CheckCollationReturnsLargeInteger(self): + def mycoll(x, y): + # reverse order + return -((x > y) - (x < y)) * 2**32 + con = sqlite.connect(":memory:") + con.create_collation("mycoll", mycoll) + sql = """ + select x from ( + select 'a' as x + union + select 'b' as x + union + select 'c' as x + ) order by x collate mycoll + """ + result = con.execute(sql).fetchall() + self.assertEqual(result, [('c',), ('b',), ('a',)], + msg="the expected order was not returned") + def CheckCollationRegisterTwice(self): """ Register two different collation functions under the same name. diff --git a/lib-python/2.7/sqlite3/test/regression.py b/lib-python/2.7/sqlite3/test/regression.py --- a/lib-python/2.7/sqlite3/test/regression.py +++ b/lib-python/2.7/sqlite3/test/regression.py @@ -1,4 +1,4 @@ -#-*- coding: ISO-8859-1 -*- +#-*- coding: iso-8859-1 -*- # pysqlite2/test/regression.py: pysqlite regression tests # # Copyright (C) 2006-2007 Gerhard H?ring @@ -285,6 +285,32 @@ cur.executemany("insert into b (baz) values (?)", ((i,) for i in foo())) + def CheckConvertTimestampMicrosecondPadding(self): + """ + http://bugs.python.org/issue14720 + + The microsecond parsing of convert_timestamp() should pad with zeros, + since the microsecond string "456" actually represents "456000". + """ + + con = sqlite.connect(":memory:", detect_types=sqlite.PARSE_DECLTYPES) + cur = con.cursor() + cur.execute("CREATE TABLE t (x TIMESTAMP)") + + # Microseconds should be 456000 + cur.execute("INSERT INTO t (x) VALUES ('2012-04-04 15:06:00.456')") + + # Microseconds should be truncated to 123456 + cur.execute("INSERT INTO t (x) VALUES ('2012-04-04 15:06:00.123456789')") + + cur.execute("SELECT * FROM t") + values = [x[0] for x in cur.fetchall()] + + self.assertEqual(values, [ + datetime.datetime(2012, 4, 4, 15, 6, 0, 456000), + datetime.datetime(2012, 4, 4, 15, 6, 0, 123456), + ]) + def suite(): regression_suite = unittest.makeSuite(RegressionTests, "Check") diff --git a/lib-python/2.7/sqlite3/test/userfunctions.py b/lib-python/2.7/sqlite3/test/userfunctions.py --- a/lib-python/2.7/sqlite3/test/userfunctions.py +++ b/lib-python/2.7/sqlite3/test/userfunctions.py @@ -374,14 +374,15 @@ val = cur.fetchone()[0] self.assertEqual(val, 60) -def authorizer_cb(action, arg1, arg2, dbname, source): - if action != sqlite.SQLITE_SELECT: - return sqlite.SQLITE_DENY - if arg2 == 'c2' or arg1 == 't2': - return sqlite.SQLITE_DENY - return sqlite.SQLITE_OK +class AuthorizerTests(unittest.TestCase): + @staticmethod + def authorizer_cb(action, arg1, arg2, dbname, source): + if action != sqlite.SQLITE_SELECT: + return sqlite.SQLITE_DENY + if arg2 == 'c2' or arg1 == 't2': + return sqlite.SQLITE_DENY + return sqlite.SQLITE_OK -class AuthorizerTests(unittest.TestCase): def setUp(self): self.con = sqlite.connect(":memory:") self.con.executescript(""" @@ -394,12 +395,12 @@ # For our security test: self.con.execute("select c2 from t2") - self.con.set_authorizer(authorizer_cb) + self.con.set_authorizer(self.authorizer_cb) def tearDown(self): pass - def CheckTableAccess(self): + def test_table_access(self): try: self.con.execute("select * from t2") except sqlite.DatabaseError, e: @@ -408,7 +409,7 @@ return self.fail("should have raised an exception due to missing privileges") - def CheckColumnAccess(self): + def test_column_access(self): try: self.con.execute("select c2 from t1") except sqlite.DatabaseError, e: @@ -417,11 +418,46 @@ return self.fail("should have raised an exception due to missing privileges") +class AuthorizerRaiseExceptionTests(AuthorizerTests): + @staticmethod + def authorizer_cb(action, arg1, arg2, dbname, source): + if action != sqlite.SQLITE_SELECT: + raise ValueError + if arg2 == 'c2' or arg1 == 't2': + raise ValueError + return sqlite.SQLITE_OK + +class AuthorizerIllegalTypeTests(AuthorizerTests): + @staticmethod + def authorizer_cb(action, arg1, arg2, dbname, source): + if action != sqlite.SQLITE_SELECT: + return 0.0 + if arg2 == 'c2' or arg1 == 't2': + return 0.0 + return sqlite.SQLITE_OK + +class AuthorizerLargeIntegerTests(AuthorizerTests): + @staticmethod + def authorizer_cb(action, arg1, arg2, dbname, source): + if action != sqlite.SQLITE_SELECT: + return 2**32 + if arg2 == 'c2' or arg1 == 't2': + return 2**32 + return sqlite.SQLITE_OK + + def suite(): function_suite = unittest.makeSuite(FunctionTests, "Check") aggregate_suite = unittest.makeSuite(AggregateTests, "Check") - authorizer_suite = unittest.makeSuite(AuthorizerTests, "Check") - return unittest.TestSuite((function_suite, aggregate_suite, authorizer_suite)) + authorizer_suite = unittest.makeSuite(AuthorizerTests) + return unittest.TestSuite(( + function_suite, + aggregate_suite, + authorizer_suite, + unittest.makeSuite(AuthorizerRaiseExceptionTests), + unittest.makeSuite(AuthorizerIllegalTypeTests), + unittest.makeSuite(AuthorizerLargeIntegerTests), + )) def test(): runner = unittest.TextTestRunner() diff --git a/lib-python/2.7/sre_compile.py b/lib-python/2.7/sre_compile.py --- a/lib-python/2.7/sre_compile.py +++ b/lib-python/2.7/sre_compile.py @@ -13,6 +13,7 @@ import _sre, sys import sre_parse from sre_constants import * +from _sre import MAXREPEAT assert _sre.MAGIC == MAGIC, "SRE module mismatch" diff --git a/lib-python/2.7/sre_constants.py b/lib-python/2.7/sre_constants.py --- a/lib-python/2.7/sre_constants.py +++ b/lib-python/2.7/sre_constants.py @@ -15,9 +15,7 @@ MAGIC = 20031017 -# max code word in this release - -MAXREPEAT = 65535 +from _sre import MAXREPEAT # SRE standard exception (access as sre.error) # should this really be here? diff --git a/lib-python/2.7/sre_parse.py b/lib-python/2.7/sre_parse.py --- a/lib-python/2.7/sre_parse.py +++ b/lib-python/2.7/sre_parse.py @@ -15,6 +15,7 @@ import sys from sre_constants import * +from _sre import MAXREPEAT SPECIAL_CHARS = ".\\[{()*+?^$|" REPEAT_CHARS = "*+?{" @@ -228,7 +229,7 @@ if code: return code code = CATEGORIES.get(escape) - if code: + if code and code[0] == IN: return code try: c = escape[1:2] @@ -498,10 +499,14 @@ continue if lo: min = int(lo) + if min >= MAXREPEAT: + raise OverflowError("the repetition number is too large") if hi: max = int(hi) - if max < min: - raise error, "bad repeat interval" + if max >= MAXREPEAT: + raise OverflowError("the repetition number is too large") + if max < min: + raise error("bad repeat interval") else: raise error, "not supported" # figure out which item to repeat @@ -541,6 +546,8 @@ break name = name + char group = 1 + if not name: + raise error("missing group name") if not isname(name): raise error, "bad character in group name" elif sourcematch("="): @@ -553,6 +560,8 @@ if char == ")": break name = name + char + if not name: + raise error("missing group name") if not isname(name): raise error, "bad character in group name" gid = state.groupdict.get(name) @@ -605,6 +614,8 @@ break condname = condname + char group = 2 + if not condname: + raise error("missing group name") if isname(condname): condgroup = state.groupdict.get(condname) if condgroup is None: @@ -723,7 +734,7 @@ break name = name + char if not name: - raise error, "bad group name" + raise error, "missing group name" try: index = int(name) if index < 0: diff --git a/lib-python/2.7/ssl.py b/lib-python/2.7/ssl.py --- a/lib-python/2.7/ssl.py +++ b/lib-python/2.7/ssl.py @@ -313,17 +313,19 @@ self.cert_reqs, self.ssl_version, self.ca_certs, self.ciphers) try: - socket.connect(self, addr) - if self.do_handshake_on_connect: - self.do_handshake() - except socket_error as e: if return_errno: - return e.errno + rc = socket.connect_ex(self, addr) else: - self._sslobj = None - raise e - self._connected = True - return 0 + rc = None + socket.connect(self, addr) + if not rc: + if self.do_handshake_on_connect: + self.do_handshake() + self._connected = True + return rc + except socket_error: + self._sslobj = None + raise def connect(self, addr): """Connects to remote ADDR, and then wraps the connection in diff --git a/lib-python/2.7/string.py b/lib-python/2.7/string.py --- a/lib-python/2.7/string.py +++ b/lib-python/2.7/string.py @@ -601,12 +601,12 @@ def convert_field(self, value, conversion): # do any conversion on the resulting object - if conversion == 'r': - return repr(value) + if conversion is None: + return value elif conversion == 's': return str(value) - elif conversion is None: - return value + elif conversion == 'r': + return repr(value) raise ValueError("Unknown conversion specifier {0!s}".format(conversion)) diff --git a/lib-python/2.7/subprocess.py b/lib-python/2.7/subprocess.py --- a/lib-python/2.7/subprocess.py +++ b/lib-python/2.7/subprocess.py @@ -671,12 +671,33 @@ c2pread, c2pwrite, errread, errwrite) = self._get_handles(stdin, stdout, stderr) - self._execute_child(args, executable, preexec_fn, close_fds, - cwd, env, universal_newlines, - startupinfo, creationflags, shell, - p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) + try: + self._execute_child(args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + except Exception: + # Preserve original exception in case os.close raises. + exc_type, exc_value, exc_trace = sys.exc_info() + + to_close = [] + # Only close the pipes we created. + if stdin == PIPE: + to_close.extend((p2cread, p2cwrite)) + if stdout == PIPE: + to_close.extend((c2pread, c2pwrite)) + if stderr == PIPE: + to_close.extend((errread, errwrite)) + + for fd in to_close: + try: + os.close(fd) + except EnvironmentError: + pass + + raise exc_type, exc_value, exc_trace if mswindows: if p2cwrite is not None: @@ -1253,9 +1274,6 @@ if e.errno != errno.ECHILD: raise child_exception = pickle.loads(data) - for fd in (p2cwrite, c2pread, errread): - if fd is not None: - os.close(fd) raise child_exception @@ -1274,7 +1292,7 @@ def _internal_poll(self, _deadstate=None, _waitpid=os.waitpid, - _WNOHANG=os.WNOHANG, _os_error=os.error): + _WNOHANG=os.WNOHANG, _os_error=os.error, _ECHILD=errno.ECHILD): """Check if child process has terminated. Returns returncode attribute. @@ -1287,16 +1305,23 @@ pid, sts = _waitpid(self.pid, _WNOHANG) if pid == self.pid: self._handle_exitstatus(sts) - except _os_error: + except _os_error as e: if _deadstate is not None: self.returncode = _deadstate + if e.errno == _ECHILD: + # This happens if SIGCLD is set to be ignored or + # waiting for child processes has otherwise been + # disabled for our process. This child is dead, we + # can't get the status. + # http://bugs.python.org/issue15756 + self.returncode = 0 return self.returncode def wait(self): """Wait for child process to terminate. Returns returncode attribute.""" - if self.returncode is None: + while self.returncode is None: try: pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0) except OSError as e: @@ -1305,8 +1330,12 @@ # This happens if SIGCLD is set to be ignored or waiting # for child processes has otherwise been disabled for our # process. This child is dead, we can't get the status. + pid = self.pid sts = 0 - self._handle_exitstatus(sts) + # Check the pid and loop as waitpid has been known to return + # 0 even without WNOHANG in odd situations. issue14396. + if pid == self.pid: + self._handle_exitstatus(sts) return self.returncode diff --git a/lib-python/2.7/sysconfig.py b/lib-python/2.7/sysconfig.py --- a/lib-python/2.7/sysconfig.py +++ b/lib-python/2.7/sysconfig.py @@ -116,6 +116,10 @@ if os.name == "nt" and "\\pcbuild\\amd64" in _PROJECT_BASE[-14:].lower(): _PROJECT_BASE = _safe_realpath(os.path.join(_PROJECT_BASE, pardir, pardir)) +# set for cross builds +if "_PYTHON_PROJECT_BASE" in os.environ: + # the build directory for posix builds + _PROJECT_BASE = os.path.normpath(os.path.abspath(".")) def is_python_build(): for fn in ("Setup.dist", "Setup.local"): if os.path.isfile(os.path.join(_PROJECT_BASE, "Modules", fn)): @@ -445,64 +449,11 @@ srcdir = os.path.join(base, _CONFIG_VARS['srcdir']) _CONFIG_VARS['srcdir'] = os.path.normpath(srcdir) + # OS X platforms require special customization to handle + # multi-architecture, multi-os-version installers if sys.platform == 'darwin': - kernel_version = os.uname()[2] # Kernel version (8.4.3) - major_version = int(kernel_version.split('.')[0]) - - if major_version < 8: - # On Mac OS X before 10.4, check if -arch and -isysroot - # are in CFLAGS or LDFLAGS and remove them if they are. - # This is needed when building extensions on a 10.3 system - # using a universal build of python. - for key in ('LDFLAGS', 'BASECFLAGS', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - flags = _CONFIG_VARS[key] - flags = re.sub('-arch\s+\w+\s', ' ', flags) - flags = re.sub('-isysroot [^ \t]*', ' ', flags) - _CONFIG_VARS[key] = flags - else: - # Allow the user to override the architecture flags using - # an environment variable. - # NOTE: This name was introduced by Apple in OSX 10.5 and - # is used by several scripting languages distributed with - # that OS release. - if 'ARCHFLAGS' in os.environ: - arch = os.environ['ARCHFLAGS'] - for key in ('LDFLAGS', 'BASECFLAGS', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - - flags = _CONFIG_VARS[key] - flags = re.sub('-arch\s+\w+\s', ' ', flags) - flags = flags + ' ' + arch - _CONFIG_VARS[key] = flags - - # If we're on OSX 10.5 or later and the user tries to - # compiles an extension using an SDK that is not present - # on the current machine it is better to not use an SDK - # than to fail. - # - # The major usecase for this is users using a Python.org - # binary installer on OSX 10.6: that installer uses - # the 10.4u SDK, but that SDK is not installed by default - # when you install Xcode. - # - CFLAGS = _CONFIG_VARS.get('CFLAGS', '') - m = re.search('-isysroot\s+(\S+)', CFLAGS) - if m is not None: - sdk = m.group(1) - if not os.path.exists(sdk): - for key in ('LDFLAGS', 'BASECFLAGS', - # a number of derived variables. These need to be - # patched up as well. - 'CFLAGS', 'PY_CFLAGS', 'BLDSHARED'): - - flags = _CONFIG_VARS[key] - flags = re.sub('-isysroot\s+\S+(\s|$)', ' ', flags) - _CONFIG_VARS[key] = flags + import _osx_support + _osx_support.customize_config_vars(_CONFIG_VARS) if args: vals = [] @@ -560,6 +511,10 @@ return 'win-ia64' return sys.platform + # Set for cross builds explicitly + if "_PYTHON_HOST_PLATFORM" in os.environ: + return os.environ["_PYTHON_HOST_PLATFORM"] + if os.name != "posix" or not hasattr(os, 'uname'): # XXX what about the architecture? NT is Intel or Alpha, # Mac OS is M68k or PPC, etc. @@ -600,91 +555,10 @@ if m: release = m.group() elif osname[:6] == "darwin": - # - # For our purposes, we'll assume that the system version from - # distutils' perspective is what MACOSX_DEPLOYMENT_TARGET is set - # to. This makes the compatibility story a bit more sane because the - # machine is going to compile and link as if it were - # MACOSX_DEPLOYMENT_TARGET. - cfgvars = get_config_vars() - macver = cfgvars.get('MACOSX_DEPLOYMENT_TARGET') - - if 1: - # Always calculate the release of the running machine, - # needed to determine if we can build fat binaries or not. - - macrelease = macver - # Get the system version. Reading this plist is a documented - # way to get the system version (see the documentation for - # the Gestalt Manager) - try: - f = open('/System/Library/CoreServices/SystemVersion.plist') - except IOError: - # We're on a plain darwin box, fall back to the default - # behaviour. - pass - else: - try: - m = re.search( - r'ProductUserVisibleVersion\s*' + - r'(.*?)', f.read()) - if m is not None: - macrelease = '.'.join(m.group(1).split('.')[:2]) - # else: fall back to the default behaviour - finally: - f.close() - - if not macver: - macver = macrelease - - if macver: - release = macver - osname = "macosx" - - if (macrelease + '.') >= '10.4.' and \ - '-arch' in get_config_vars().get('CFLAGS', '').strip(): - # The universal build will build fat binaries, but not on - # systems before 10.4 - # - # Try to detect 4-way universal builds, those have machine-type - # 'universal' instead of 'fat'. - - machine = 'fat' - cflags = get_config_vars().get('CFLAGS') - - archs = re.findall('-arch\s+(\S+)', cflags) - archs = tuple(sorted(set(archs))) - - if len(archs) == 1: - machine = archs[0] - elif archs == ('i386', 'ppc'): - machine = 'fat' - elif archs == ('i386', 'x86_64'): - machine = 'intel' - elif archs == ('i386', 'ppc', 'x86_64'): - machine = 'fat3' - elif archs == ('ppc64', 'x86_64'): - machine = 'fat64' - elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'): - machine = 'universal' - else: - raise ValueError( - "Don't know machine value for archs=%r"%(archs,)) - - elif machine == 'i386': - # On OSX the machine type returned by uname is always the - # 32-bit variant, even if the executable architecture is - # the 64-bit variant - if sys.maxint >= 2**32: - machine = 'x86_64' - - elif machine in ('PowerPC', 'Power_Macintosh'): - # Pick a sane name for the PPC architecture. - # See 'i386' case - if sys.maxint >= 2**32: - machine = 'ppc64' - else: - machine = 'ppc' + import _osx_support + osname, release, machine = _osx_support.get_platform_osx( + get_config_vars(), + osname, release, machine) return "%s-%s-%s" % (osname, release, machine) diff --git a/lib-python/2.7/tarfile.py b/lib-python/2.7/tarfile.py --- a/lib-python/2.7/tarfile.py +++ b/lib-python/2.7/tarfile.py @@ -1987,9 +1987,8 @@ # Append the tar header and data to the archive. if tarinfo.isreg(): - f = bltn_open(name, "rb") - self.addfile(tarinfo, f) - f.close() + with bltn_open(name, "rb") as f: + self.addfile(tarinfo, f) elif tarinfo.isdir(): self.addfile(tarinfo) @@ -2197,10 +2196,11 @@ """Make a file called targetpath. """ source = self.extractfile(tarinfo) - target = bltn_open(targetpath, "wb") - copyfileobj(source, target) - source.close() - target.close() + try: + with bltn_open(targetpath, "wb") as target: + copyfileobj(source, target) + finally: + source.close() def makeunknown(self, tarinfo, targetpath): """Make a file from a TarInfo object with an unknown type @@ -2397,7 +2397,7 @@ """ if tarinfo.issym(): # Always search the entire archive. - linkname = os.path.dirname(tarinfo.name) + "/" + tarinfo.linkname + linkname = "/".join(filter(None, (os.path.dirname(tarinfo.name), tarinfo.linkname))) limit = None else: # Search the archive before the link, because a hard link is diff --git a/lib-python/2.7/telnetlib.py b/lib-python/2.7/telnetlib.py --- a/lib-python/2.7/telnetlib.py +++ b/lib-python/2.7/telnetlib.py @@ -34,6 +34,7 @@ # Imported modules +import errno import sys import socket import select @@ -205,6 +206,7 @@ self.sb = 0 # flag for SB and SE sequence. self.sbdataq = '' self.option_callback = None + self._has_poll = hasattr(select, 'poll') if host is not None: self.open(host, port, timeout) @@ -287,6 +289,61 @@ is closed and no cooked data is available. """ + if self._has_poll: + return self._read_until_with_poll(match, timeout) + else: + return self._read_until_with_select(match, timeout) + + def _read_until_with_poll(self, match, timeout): + """Read until a given string is encountered or until timeout. + + This method uses select.poll() to implement the timeout. + """ + n = len(match) + call_timeout = timeout + if timeout is not None: + from time import time + time_start = time() + self.process_rawq() + i = self.cookedq.find(match) + if i < 0: + poller = select.poll() + poll_in_or_priority_flags = select.POLLIN | select.POLLPRI + poller.register(self, poll_in_or_priority_flags) + while i < 0 and not self.eof: + try: + ready = poller.poll(call_timeout) + except select.error as e: + if e.errno == errno.EINTR: + if timeout is not None: + elapsed = time() - time_start + call_timeout = timeout-elapsed + continue + raise + for fd, mode in ready: + if mode & poll_in_or_priority_flags: + i = max(0, len(self.cookedq)-n) + self.fill_rawq() + self.process_rawq() + i = self.cookedq.find(match, i) + if timeout is not None: + elapsed = time() - time_start + if elapsed >= timeout: + break + call_timeout = timeout-elapsed + poller.unregister(self) + if i >= 0: + i = i + n + buf = self.cookedq[:i] + self.cookedq = self.cookedq[i:] + return buf + return self.read_very_lazy() + + def _read_until_with_select(self, match, timeout=None): + """Read until a given string is encountered or until timeout. + + The timeout is implemented using select.select(). + """ n = len(match) self.process_rawq() i = self.cookedq.find(match) @@ -589,6 +646,79 @@ results are undeterministic, and may depend on the I/O timing. """ + if self._has_poll: + return self._expect_with_poll(list, timeout) + else: + return self._expect_with_select(list, timeout) + + def _expect_with_poll(self, expect_list, timeout=None): + """Read until one from a list of a regular expressions matches. + + This method uses select.poll() to implement the timeout. + """ + re = None + expect_list = expect_list[:] + indices = range(len(expect_list)) + for i in indices: + if not hasattr(expect_list[i], "search"): + if not re: import re + expect_list[i] = re.compile(expect_list[i]) + call_timeout = timeout + if timeout is not None: + from time import time + time_start = time() + self.process_rawq() + m = None + for i in indices: + m = expect_list[i].search(self.cookedq) + if m: + e = m.end() + text = self.cookedq[:e] + self.cookedq = self.cookedq[e:] + break + if not m: + poller = select.poll() + poll_in_or_priority_flags = select.POLLIN | select.POLLPRI + poller.register(self, poll_in_or_priority_flags) + while not m and not self.eof: + try: + ready = poller.poll(call_timeout) + except select.error as e: + if e.errno == errno.EINTR: + if timeout is not None: + elapsed = time() - time_start + call_timeout = timeout-elapsed + continue + raise + for fd, mode in ready: + if mode & poll_in_or_priority_flags: + self.fill_rawq() + self.process_rawq() + for i in indices: + m = expect_list[i].search(self.cookedq) + if m: + e = m.end() + text = self.cookedq[:e] + self.cookedq = self.cookedq[e:] + break + if timeout is not None: + elapsed = time() - time_start + if elapsed >= timeout: + break + call_timeout = timeout-elapsed + poller.unregister(self) + if m: + return (i, m, text) + text = self.read_very_lazy() + if not text and self.eof: + raise EOFError + return (-1, None, text) + + def _expect_with_select(self, list, timeout=None): + """Read until one from a list of a regular expressions matches. + + The timeout is implemented using select.select(). + """ re = None list = list[:] indices = range(len(list)) diff --git a/lib-python/2.7/tempfile.py b/lib-python/2.7/tempfile.py --- a/lib-python/2.7/tempfile.py +++ b/lib-python/2.7/tempfile.py @@ -29,6 +29,7 @@ # Imports. +import io as _io import os as _os import errno as _errno from random import Random as _Random @@ -193,15 +194,18 @@ name = namer.next() filename = _os.path.join(dir, name) try: - fd = _os.open(filename, flags, 0600) - fp = _os.fdopen(fd, 'w') - fp.write('blat') - fp.close() - _os.unlink(filename) - del fp, fd + fd = _os.open(filename, flags, 0o600) + try: + try: + with _io.open(fd, 'wb', closefd=False) as fp: + fp.write(b'blat') + finally: + _os.close(fd) + finally: + _os.unlink(filename) return dir - except (OSError, IOError), e: - if e[0] != _errno.EEXIST: + except (OSError, IOError) as e: + if e.args[0] != _errno.EEXIST: break # no point trying more names in this directory pass raise IOError, (_errno.ENOENT, @@ -546,10 +550,6 @@ def closed(self): return self._file.closed - @property - def encoding(self): - return self._file.encoding - def fileno(self): self.rollover() return self._file.fileno() @@ -562,15 +562,17 @@ @property def mode(self): - return self._file.mode + try: + return self._file.mode + except AttributeError: + return self._TemporaryFileArgs[0] @property def name(self): - return self._file.name - - @property - def newlines(self): - return self._file.newlines + try: + return self._file.name + except AttributeError: + return None def next(self): return self._file.next @@ -610,4 +612,7 @@ return rv def xreadlines(self, *args): - return self._file.xreadlines(*args) + try: + return self._file.xreadlines(*args) + except AttributeError: + return iter(self._file.readlines(*args)) diff --git a/lib-python/2.7/test/crashers/buffer_mutate.py b/lib-python/2.7/test/crashers/buffer_mutate.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/crashers/buffer_mutate.py @@ -0,0 +1,30 @@ +# +# The various methods of bufferobject.c (here buffer_subscript()) call +# get_buf() before calling potentially more Python code (here via +# PySlice_GetIndicesEx()). But get_buf() already returned a void* +# pointer. This void* pointer can become invalid if the object +# underlying the buffer is mutated (here a bytearray object). +# +# As usual, please keep in mind that the three "here" in the sentence +# above are only examples. Each can be changed easily and lead to +# another crasher. +# +# This crashes for me on Linux 32-bits with CPython 2.6 and 2.7 +# with a segmentation fault. +# + + +class PseudoIndex(object): + def __index__(self): + for c in "foobar"*n: + a.append(c) + return n * 4 + + +for n in range(1, 100000, 100): + a = bytearray("test"*n) + buf = buffer(a) + + s = buf[:PseudoIndex():1] + #print repr(s) + #assert s == "test"*n diff --git a/lib-python/2.7/test/keycert.pem b/lib-python/2.7/test/keycert.pem --- a/lib-python/2.7/test/keycert.pem +++ b/lib-python/2.7/test/keycert.pem @@ -1,32 +1,31 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXwIBAAKBgQC8ddrhm+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9L -opdJhTvbGfEj0DQs1IE8M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVH -fhi/VwovESJlaBOp+WMnfhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQAB -AoGBAK0FZpaKj6WnJZN0RqhhK+ggtBWwBnc0U/ozgKz2j1s3fsShYeiGtW6CK5nU -D1dZ5wzhbGThI7LiOXDvRucc9n7vUgi0alqPQ/PFodPxAN/eEYkmXQ7W2k7zwsDA -IUK0KUhktQbLu8qF/m8qM86ba9y9/9YkXuQbZ3COl5ahTZrhAkEA301P08RKv3KM -oXnGU2UHTuJ1MAD2hOrPxjD4/wxA/39EWG9bZczbJyggB4RHu0I3NOSFjAm3HQm0 -ANOu5QK9owJBANgOeLfNNcF4pp+UikRFqxk5hULqRAWzVxVrWe85FlPm0VVmHbb/ -loif7mqjU8o1jTd/LM7RD9f2usZyE2psaw8CQQCNLhkpX3KO5kKJmS9N7JMZSc4j -oog58yeYO8BBqKKzpug0LXuQultYv2K4veaIO04iL9VLe5z9S/Q1jaCHBBuXAkEA -z8gjGoi1AOp6PBBLZNsncCvcV/0aC+1se4HxTNo2+duKSDnbq+ljqOM+E7odU+Nq -ewvIWOG//e8fssd0mq3HywJBAJ8l/c8GVmrpFTx8r/nZ2Pyyjt3dH1widooDXYSV -q6Gbf41Llo5sYAtmxdndTLASuHKecacTgZVhy0FryZpLKrU= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANtb0+YrKuxevGpm +LrjaUhZSgz6zFAmuGFmKmUbdjmfv9zSmmdsQIksK++jK0Be9LeZy20j6ahOfuVa0 +ufEmPoP7Fy4hXegKZR9cCWcIe/A6H2xWF1IIJLRTLaU8ol/I7T+um5HD5AwAwNPP +USNU0Eegmvp+xxWu3NX2m1Veot85AgMBAAECgYA3ZdZ673X0oexFlq7AAmrutkHt +CL7LvwrpOiaBjhyTxTeSNWzvtQBkIU8DOI0bIazA4UreAFffwtvEuPmonDb3F+Iq +SMAu42XcGyVZEl+gHlTPU9XRX7nTOXVt+MlRRRxL6t9GkGfUAXI3XxJDXW3c0vBK +UL9xqD8cORXOfE06rQJBAP8mEX1ERkR64Ptsoe4281vjTlNfIbs7NMPkUnrn9N/Y +BLhjNIfQ3HFZG8BTMLfX7kCS9D593DW5tV4Z9BP/c6cCQQDcFzCcVArNh2JSywOQ +ZfTfRbJg/Z5Lt9Fkngv1meeGNPgIMLN8Sg679pAOOWmzdMO3V706rNPzSVMME7E5 +oPIfAkEA8pDddarP5tCvTTgUpmTFbakm0KoTZm2+FzHcnA4jRh+XNTjTOv98Y6Ik +eO5d1ZnKXseWvkZncQgxfdnMqqpj5wJAcNq/RVne1DbYlwWchT2Si65MYmmJ8t+F +0mcsULqjOnEMwf5e+ptq5LzwbyrHZYq5FNk7ocufPv/ZQrcSSC+cFwJBAKvOJByS +x56qyGeZLOQlWS2JS3KJo59XuLFGqcbgN9Om9xFa41Yb4N9NvplFivsvZdw3m1Q/ +SPIXQuT8RMPDVNQ= +-----END PRIVATE KEY----- -----BEGIN CERTIFICATE----- -MIICpzCCAhCgAwIBAgIJAP+qStv1cIGNMA0GCSqGSIb3DQEBBQUAMIGJMQswCQYD -VQQGEwJVUzERMA8GA1UECBMIRGVsYXdhcmUxEzARBgNVBAcTCldpbG1pbmd0b24x -IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMQwwCgYDVQQLEwNT -U0wxHzAdBgNVBAMTFnNvbWVtYWNoaW5lLnB5dGhvbi5vcmcwHhcNMDcwODI3MTY1 -NDUwWhcNMTMwMjE2MTY1NDUwWjCBiTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCERl -bGF3YXJlMRMwEQYDVQQHEwpXaWxtaW5ndG9uMSMwIQYDVQQKExpQeXRob24gU29m -dHdhcmUgRm91bmRhdGlvbjEMMAoGA1UECxMDU1NMMR8wHQYDVQQDExZzb21lbWFj -aGluZS5weXRob24ub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ddrh -m+LutBvjYcQlnH21PPIseJ1JVG2HMmN2CmZk2YukO+9LopdJhTvbGfEj0DQs1IE8 -M+kTUyOmuKfVrFMKwtVeCJphrAnhoz7TYOuLBSqt7lVHfhi/VwovESJlaBOp+WMn -fhcduPEYHYx/6cnVapIkZnLt30zu2um+DzA9jQIDAQABoxUwEzARBglghkgBhvhC -AQEEBAMCBkAwDQYJKoZIhvcNAQEFBQADgYEAF4Q5BVqmCOLv1n8je/Jw9K669VXb -08hyGzQhkemEBYQd6fzQ9A/1ZzHkJKb1P6yreOLSEh4KcxYPyrLRC1ll8nr5OlCx -CMhKkTnR6qBsdNV0XtdU2+N25hqW+Ma4ZeqsN/iiJVCGNOZGnvQuvCAGWF8+J/f/ -iHkC6gGdBJhogs4= +MIICVDCCAb2gAwIBAgIJANfHOBkZr8JOMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV +BAYTAlhZMRcwFQYDVQQHEw5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9u +IFNvZnR3YXJlIEZvdW5kYXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMDEw +MDgyMzAxNTZaFw0yMDEwMDUyMzAxNTZaMF8xCzAJBgNVBAYTAlhZMRcwFQYDVQQH +Ew5DYXN0bGUgQW50aHJheDEjMCEGA1UEChMaUHl0aG9uIFNvZnR3YXJlIEZvdW5k +YXRpb24xEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAw +gYkCgYEA21vT5isq7F68amYuuNpSFlKDPrMUCa4YWYqZRt2OZ+/3NKaZ2xAiSwr7 +6MrQF70t5nLbSPpqE5+5VrS58SY+g/sXLiFd6AplH1wJZwh78DofbFYXUggktFMt +pTyiX8jtP66bkcPkDADA089RI1TQR6Ca+n7HFa7c1fabVV6i3zkCAwEAAaMYMBYw +FAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEBBQUAA4GBAHPctQBEQ4wd +BJ6+JcpIraopLn8BGhbjNWj40mmRqWB/NAWF6M5ne7KpGAu7tLeG4hb1zLaldK8G +lxy2GPSRF6LFS48dpEj2HbMv2nvv6xxalDMJ9+DicWgAKTQ6bcX2j3GUkCR0g/T1 +CRlNBAAlvhKzO7Clpf9l0YKBEfraJByX -----END CERTIFICATE----- diff --git a/lib-python/2.7/test/mp_fork_bomb.py b/lib-python/2.7/test/mp_fork_bomb.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/mp_fork_bomb.py @@ -0,0 +1,16 @@ +import multiprocessing + +def foo(conn): + conn.send("123") + +# Because "if __name__ == '__main__'" is missing this will not work +# correctly on Windows. However, we should get a RuntimeError rather +# than the Windows equivalent of a fork bomb. + +r, w = multiprocessing.Pipe(False) +p = multiprocessing.Process(target=foo, args=(w,)) +p.start() +w.close() +print(r.recv()) +r.close() +p.join() diff --git a/lib-python/2.7/test/pickletester.py b/lib-python/2.7/test/pickletester.py --- a/lib-python/2.7/test/pickletester.py +++ b/lib-python/2.7/test/pickletester.py @@ -6,7 +6,8 @@ import pickletools import copy_reg -from test.test_support import TestFailed, have_unicode, TESTFN +from test.test_support import (TestFailed, have_unicode, TESTFN, _2G, _1M, + precisionbigmemtest) # Tests that try a number of pickle protocols should have a # for proto in protocols: @@ -502,10 +503,10 @@ i = C() i.attr = i for proto in protocols: - s = self.dumps(i, 2) + s = self.dumps(i, proto) x = self.loads(s) self.assertEqual(dir(x), dir(i)) - self.assertTrue(x.attr is x) + self.assertIs(x.attr, x) def test_recursive_multi(self): l = [] @@ -1280,3 +1281,31 @@ f.write(pickled2) f.seek(0) self.assertEqual(unpickler.load(), data2) + +class BigmemPickleTests(unittest.TestCase): + + # Memory requirements: 1 byte per character for input strings, 1 byte + # for pickled data, 1 byte for unpickled strings, 1 byte for internal + # buffer and 1 byte of free space for resizing of internal buffer. + + @precisionbigmemtest(size=_2G + 100*_1M, memuse=5) + def test_huge_strlist(self, size): + chunksize = 2**20 + data = [] + while size > chunksize: + data.append('x' * chunksize) + size -= chunksize + chunksize += 1 + data.append('y' * size) + + try: + for proto in protocols: + try: + pickled = self.dumps(data, proto) + res = self.loads(pickled) + self.assertEqual(res, data) + finally: + res = None + pickled = None + finally: + data = None diff --git a/lib-python/2.7/test/regrtest.py b/lib-python/2.7/test/regrtest.py --- a/lib-python/2.7/test/regrtest.py +++ b/lib-python/2.7/test/regrtest.py @@ -32,7 +32,7 @@ Selecting tests --r/--random -- randomize test execution order (see below) +-r/--randomize -- randomize test execution order (see below) --randseed -- pass a random seed to reproduce a previous random run -f/--fromfile -- read names of tests to run from a file (see below) -x/--exclude -- arguments are tests to *exclude* @@ -158,6 +158,7 @@ import os import random import re +import shutil import sys import time import traceback @@ -258,7 +259,7 @@ try: opts, args = getopt.getopt(sys.argv[1:], 'hvqxsSrf:lu:t:TD:NLR:FwWM:j:', ['help', 'verbose', 'verbose2', 'verbose3', 'quiet', - 'exclude', 'single', 'slow', 'random', 'fromfile', 'findleaks', + 'exclude', 'single', 'slow', 'randomize', 'fromfile=', 'findleaks', 'use=', 'threshold=', 'trace', 'coverdir=', 'nocoverdir', 'runleaks', 'huntrleaks=', 'memlimit=', 'randseed=', 'multiprocess=', 'slaveargs=', 'forever', 'header']) @@ -540,6 +541,8 @@ print stdout if stderr: print >>sys.stderr, stderr + sys.stdout.flush() + sys.stderr.flush() if result[0] == INTERRUPTED: assert result[1] == 'KeyboardInterrupt' raise KeyboardInterrupt # What else? @@ -941,7 +944,6 @@ return FAILED, test_time def cleanup_test_droppings(testname, verbose): - import shutil import stat import gc diff --git a/lib-python/2.7/test/sample_doctest_no_docstrings.py b/lib-python/2.7/test/sample_doctest_no_docstrings.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/sample_doctest_no_docstrings.py @@ -0,0 +1,12 @@ +# This is a sample module used for testing doctest. +# +# This module is for testing how doctest handles a module with no +# docstrings. + + +class Foo(object): + + # A class with no docstring. + + def __init__(self): + pass diff --git a/lib-python/2.7/test/sample_doctest_no_doctests.py b/lib-python/2.7/test/sample_doctest_no_doctests.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/sample_doctest_no_doctests.py @@ -0,0 +1,15 @@ +"""This is a sample module used for testing doctest. + +This module is for testing how doctest handles a module with docstrings +but no doctest examples. + +""" + + +class Foo(object): + """A docstring with no doctest examples. + + """ + + def __init__(self): + pass diff --git a/lib-python/2.7/test/script_helper.py b/lib-python/2.7/test/script_helper.py --- a/lib-python/2.7/test/script_helper.py +++ b/lib-python/2.7/test/script_helper.py @@ -10,7 +10,13 @@ import py_compile import contextlib import shutil -import zipfile +try: + import zipfile +except ImportError: + # If Python is build without Unicode support, importing _io will + # fail, which, in turn, means that zipfile cannot be imported + # Most of this module can then still be used. + pass from test.test_support import strip_python_stderr diff --git a/lib-python/2.7/test/sha256.pem b/lib-python/2.7/test/sha256.pem --- a/lib-python/2.7/test/sha256.pem +++ b/lib-python/2.7/test/sha256.pem @@ -1,129 +1,128 @@ # Certificate chain for https://sha256.tbs-internet.com - 0 s:/C=FR/postalCode=14000/ST=Calvados/L=CAEN/street=22 rue de Bretagne/O=TBS INTERNET/OU=0002 440443810/OU=sha-256 production/CN=sha256.tbs-internet.com - i:/C=FR/ST=Calvados/L=Caen/O=TBS INTERNET/OU=Terms and Conditions: http://www.tbs-internet.com/CA/repository/OU=TBS INTERNET CA/CN=TBS X509 CA SGC + 0 s:/C=FR/postalCode=14000/ST=Calvados/L=CAEN/street=22 rue de Bretagne/O=TBS INTERNET/OU=0002 440443810/OU=Certificats TBS X509/CN=ecom.tbs-x509.com + i:/C=FR/ST=Calvados/L=Caen/O=TBS INTERNET/OU=Terms and Conditions: http://www.tbs-internet.com/CA/repository/OU=TBS INTERNET CA/CN=TBS X509 CA business -----BEGIN CERTIFICATE----- -MIIGXTCCBUWgAwIBAgIRAMmag+ygSAdxZsbyzYjhuW0wDQYJKoZIhvcNAQELBQAw -gcQxCzAJBgNVBAYTAkZSMREwDwYDVQQIEwhDYWx2YWRvczENMAsGA1UEBxMEQ2Fl +MIIGTjCCBTagAwIBAgIQOh3d9dNDPq1cSdJmEiMpqDANBgkqhkiG9w0BAQUFADCB +yTELMAkGA1UEBhMCRlIxETAPBgNVBAgTCENhbHZhZG9zMQ0wCwYDVQQHEwRDYWVu +MRUwEwYDVQQKEwxUQlMgSU5URVJORVQxSDBGBgNVBAsTP1Rlcm1zIGFuZCBDb25k +aXRpb25zOiBodHRwOi8vd3d3LnRicy1pbnRlcm5ldC5jb20vQ0EvcmVwb3NpdG9y +eTEYMBYGA1UECxMPVEJTIElOVEVSTkVUIENBMR0wGwYDVQQDExRUQlMgWDUwOSBD +QSBidXNpbmVzczAeFw0xMTAxMjUwMDAwMDBaFw0xMzAyMDUyMzU5NTlaMIHHMQsw +CQYDVQQGEwJGUjEOMAwGA1UEERMFMTQwMDAxETAPBgNVBAgTCENhbHZhZG9zMQ0w +CwYDVQQHEwRDQUVOMRswGQYDVQQJExIyMiBydWUgZGUgQnJldGFnbmUxFTATBgNV +BAoTDFRCUyBJTlRFUk5FVDEXMBUGA1UECxMOMDAwMiA0NDA0NDM4MTAxHTAbBgNV +BAsTFENlcnRpZmljYXRzIFRCUyBYNTA5MRowGAYDVQQDExFlY29tLnRicy14NTA5 +LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKRrlHUnJ++1lpcg +jtYco7cdmRe+EEfTmwPfCdfV3G1QfsTSvY6FfMpm/83pqHfT+4ANwr18wD9ZrAEN +G16mf9VdCGK12+TP7DmqeZyGIqlFFoahQnmb8EarvE43/1UeQ2CV9XmzwZvpqeli +LfXsFonawrY3H6ZnMwS64St61Z+9gdyuZ/RbsoZBbT5KUjDEG844QRU4OT1IGeEI +eY5NM5RNIh6ZNhVtqeeCxMS7afONkHQrOco73RdSTRck/Hj96Ofl3MHNHryr+AMK +DGFk1kLCZGpPdXtkxXvaDeQoiYDlil26CWc+YK6xyDPMdsWvoG14ZLyCpzMXA7/7 +4YAQRH0CAwEAAaOCAjAwggIsMB8GA1UdIwQYMBaAFBoJBMz5CY+7HqDO1KQUf0vV +I1jNMB0GA1UdDgQWBBQgOU8HsWzbmD4WZP5Wtdw7jca2WDAOBgNVHQ8BAf8EBAMC +BaAwDAYDVR0TAQH/BAIwADAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +TAYDVR0gBEUwQzBBBgsrBgEEAYDlNwIBATAyMDAGCCsGAQUFBwIBFiRodHRwczov +L3d3dy50YnMtaW50ZXJuZXQuY29tL0NBL0NQUzEwdwYDVR0fBHAwbjA3oDWgM4Yx +aHR0cDovL2NybC50YnMtaW50ZXJuZXQuY29tL1RCU1g1MDlDQWJ1c2luZXNzLmNy +bDAzoDGgL4YtaHR0cDovL2NybC50YnMteDUwOS5jb20vVEJTWDUwOUNBYnVzaW5l +c3MuY3JsMIGwBggrBgEFBQcBAQSBozCBoDA9BggrBgEFBQcwAoYxaHR0cDovL2Ny +dC50YnMtaW50ZXJuZXQuY29tL1RCU1g1MDlDQWJ1c2luZXNzLmNydDA5BggrBgEF +BQcwAoYtaHR0cDovL2NydC50YnMteDUwOS5jb20vVEJTWDUwOUNBYnVzaW5lc3Mu +Y3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC50YnMteDUwOS5jb20wMwYDVR0R +BCwwKoIRZWNvbS50YnMteDUwOS5jb22CFXd3dy5lY29tLnRicy14NTA5LmNvbTAN +BgkqhkiG9w0BAQUFAAOCAQEArT4NHfbY87bGAw8lPV4DmHlmuDuVp/y7ltO3Ynse +3Rz8RxW2AzuO0Oy2F0Cu4yWKtMyEyMXyHqWtae7ElRbdTu5w5GwVBLJHClCzC8S9 +SpgMMQTx3Rgn8vjkHuU9VZQlulZyiPK7yunjc7c310S9FRZ7XxOwf8Nnx4WnB+No +WrfApzhhQl31w+RyrNxZe58hCfDDHmevRvwLjQ785ZoQXJDj2j3qAD4aI2yB8lB5 +oaE1jlCJzC7Kmz/Y9jzfmv/zAs1LQTm9ktevv4BTUFaGjv9jxnQ1xnS862ZiouLW +zZYIlYPf4F6JjXGiIQgQRglILUfq3ftJd9/ok9W9ZF8h8w== +-----END CERTIFICATE----- + 1 s:/C=FR/ST=Calvados/L=Caen/O=TBS INTERNET/OU=Terms and Conditions: http://www.tbs-internet.com/CA/repository/OU=TBS INTERNET CA/CN=TBS X509 CA business + i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root +-----BEGIN CERTIFICATE----- +MIIFPzCCBCegAwIBAgIQDlBz/++iRSmLDeVRHT/hADANBgkqhkiG9w0BAQUFADBv +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk +ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF +eHRlcm5hbCBDQSBSb290MB4XDTA1MTIwMTAwMDAwMFoXDTE5MDcwOTE4MTkyMlow +gckxCzAJBgNVBAYTAkZSMREwDwYDVQQIEwhDYWx2YWRvczENMAsGA1UEBxMEQ2Fl bjEVMBMGA1UEChMMVEJTIElOVEVSTkVUMUgwRgYDVQQLEz9UZXJtcyBhbmQgQ29u ZGl0aW9uczogaHR0cDovL3d3dy50YnMtaW50ZXJuZXQuY29tL0NBL3JlcG9zaXRv -cnkxGDAWBgNVBAsTD1RCUyBJTlRFUk5FVCBDQTEYMBYGA1UEAxMPVEJTIFg1MDkg -Q0EgU0dDMB4XDTEwMDIxODAwMDAwMFoXDTEyMDIxOTIzNTk1OVowgcsxCzAJBgNV -BAYTAkZSMQ4wDAYDVQQREwUxNDAwMDERMA8GA1UECBMIQ2FsdmFkb3MxDTALBgNV -BAcTBENBRU4xGzAZBgNVBAkTEjIyIHJ1ZSBkZSBCcmV0YWduZTEVMBMGA1UEChMM -VEJTIElOVEVSTkVUMRcwFQYDVQQLEw4wMDAyIDQ0MDQ0MzgxMDEbMBkGA1UECxMS -c2hhLTI1NiBwcm9kdWN0aW9uMSAwHgYDVQQDExdzaGEyNTYudGJzLWludGVybmV0 -LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbuM8VT7f0nntwu -N3F7v9KIBlhKNAxqCrziOXU5iqUt8HrQB3DtHbdmII+CpVUlwlmepsx6G+srEZ9a -MIGAy0nxi5aLb7watkyIdPjJTMvTUBQ/+RPWzt5JtYbbY9BlJ+yci0dctP74f4NU -ISLtlrEjUbf2gTohLrcE01TfmOF6PDEbB5PKDi38cB3NzKfizWfrOaJW6Q1C1qOJ -y4/4jkUREX1UFUIxzx7v62VfjXSGlcjGpBX1fvtABQOSLeE0a6gciDZs1REqroFf -5eXtqYphpTa14Z83ITXMfgg5Nze1VtMnzI9Qx4blYBw4dgQVEuIsYr7FDBOITDzc -VEVXZx0CAwEAAaOCAj8wggI7MB8GA1UdIwQYMBaAFAdEdoWTKLx/bXjSCuv6TEvf -2YIfMB0GA1UdDgQWBBSJKI/AYVI9RQNY0QPIqc8ej2QivTAOBgNVHQ8BAf8EBAMC -BaAwDAYDVR0TAQH/BAIwADA0BgNVHSUELTArBggrBgEFBQcDAQYIKwYBBQUHAwIG -CisGAQQBgjcKAwMGCWCGSAGG+EIEATBMBgNVHSAERTBDMEEGCysGAQQBgOU3AgQB -MDIwMAYIKwYBBQUHAgEWJGh0dHBzOi8vd3d3LnRicy1pbnRlcm5ldC5jb20vQ0Ev -Q1BTNDBtBgNVHR8EZjBkMDKgMKAuhixodHRwOi8vY3JsLnRicy1pbnRlcm5ldC5j -b20vVEJTWDUwOUNBU0dDLmNybDAuoCygKoYoaHR0cDovL2NybC50YnMteDUwOS5j -b20vVEJTWDUwOUNBU0dDLmNybDCBpgYIKwYBBQUHAQEEgZkwgZYwOAYIKwYBBQUH -MAKGLGh0dHA6Ly9jcnQudGJzLWludGVybmV0LmNvbS9UQlNYNTA5Q0FTR0MuY3J0 -MDQGCCsGAQUFBzAChihodHRwOi8vY3J0LnRicy14NTA5LmNvbS9UQlNYNTA5Q0FT -R0MuY3J0MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC50YnMteDUwOS5jb20wPwYD -VR0RBDgwNoIXc2hhMjU2LnRicy1pbnRlcm5ldC5jb22CG3d3dy5zaGEyNTYudGJz -LWludGVybmV0LmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAA5NL0D4QSqhErhlkdPmz -XtiMvdGL+ZehM4coTRIpasM/Agt36Rc0NzCvnQwKE+wkngg1Gy2qe7Q0E/ziqBtB -fZYzdVgu1zdiL4kTaf+wFKYAFGsFbyeEmXysy+CMwaNoF2vpSjCU1UD56bEnTX/W -fxVZYxtBQUpnu2wOsm8cDZuZRv9XrYgAhGj9Tt6F0aVHSDGn59uwShG1+BVF/uju -SCyPTTjL1oc7YElJUzR/x4mQJYvtQI8gDIDAGEOs7v3R/gKa5EMfbUQUI4C84UbI -Yz09Jdnws/MkC/Hm1BZEqk89u7Hvfv+oHqEb0XaUo0TDfsxE0M1sMdnLb91QNQBm -UQ== ------END CERTIFICATE----- - 1 s:/C=FR/ST=Calvados/L=Caen/O=TBS INTERNET/OU=Terms and Conditions: http://www.tbs-internet.com/CA/repository/OU=TBS INTERNET CA/CN=TBS X509 CA SGC - i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root ------BEGIN CERTIFICATE----- -MIIFVjCCBD6gAwIBAgIQXpDZ0ETJMV02WTx3GTnhhTANBgkqhkiG9w0BAQUFADBv -MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFk -ZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBF -eHRlcm5hbCBDQSBSb290MB4XDTA1MTIwMTAwMDAwMFoXDTE5MDYyNDE5MDYzMFow -gcQxCzAJBgNVBAYTAkZSMREwDwYDVQQIEwhDYWx2YWRvczENMAsGA1UEBxMEQ2Fl -bjEVMBMGA1UEChMMVEJTIElOVEVSTkVUMUgwRgYDVQQLEz9UZXJtcyBhbmQgQ29u -ZGl0aW9uczogaHR0cDovL3d3dy50YnMtaW50ZXJuZXQuY29tL0NBL3JlcG9zaXRv -cnkxGDAWBgNVBAsTD1RCUyBJTlRFUk5FVCBDQTEYMBYGA1UEAxMPVEJTIFg1MDkg -Q0EgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsgOkO3f7wzN6 -rOjg45tR5vjBfzK7qmV9IBxb/QW9EEXxG+E7FNhZqQLtwGBKoSsHTnQqV75wWMk0 -9tinWvftBkSpj5sTi/8cbzJfUvTSVYh3Qxv6AVVjMMH/ruLjE6y+4PoaPs8WoYAQ -ts5R4Z1g8c/WnTepLst2x0/Wv7GmuoQi+gXvHU6YrBiu7XkeYhzc95QdviWSJRDk -owhb5K43qhcvjRmBfO/paGlCliDGZp8mHwrI21mwobWpVjTxZRwYO3bd4+TGcI4G -Ie5wmHwE8F7SK1tgSqbBacKjDa93j7txKkfz/Yd2n7TGqOXiHPsJpG655vrKtnXk -9vs1zoDeJQIDAQABo4IBljCCAZIwHQYDVR0OBBYEFAdEdoWTKLx/bXjSCuv6TEvf -2YIfMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMCAGA1UdJQQZ -MBcGCisGAQQBgjcKAwMGCWCGSAGG+EIEATAYBgNVHSAEETAPMA0GCysGAQQBgOU3 -AgQBMHsGA1UdHwR0MHIwOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0Fk -ZFRydXN0RXh0ZXJuYWxDQVJvb3QuY3JsMDagNKAyhjBodHRwOi8vY3JsLmNvbW9k -by5uZXQvQWRkVHJ1c3RFeHRlcm5hbENBUm9vdC5jcmwwgYAGCCsGAQUFBwEBBHQw -cjA4BggrBgEFBQcwAoYsaHR0cDovL2NydC5jb21vZG9jYS5jb20vQWRkVHJ1c3RV -VE5TR0NDQS5jcnQwNgYIKwYBBQUHMAKGKmh0dHA6Ly9jcnQuY29tb2RvLm5ldC9B -ZGRUcnVzdFVUTlNHQ0NBLmNydDARBglghkgBhvhCAQEEBAMCAgQwDQYJKoZIhvcN -AQEFBQADggEBAK2zEzs+jcIrVK9oDkdDZNvhuBYTdCfpxfFs+OAujW0bIfJAy232 -euVsnJm6u/+OrqKudD2tad2BbejLLXhMZViaCmK7D9nrXHx4te5EP8rL19SUVqLY -1pTnv5dhNgEgvA7n5lIzDSYs7yRLsr7HJsYPr6SeYSuZizyX1SNz7ooJ32/F3X98 -RB0Mlc/E0OyOrkQ9/y5IrnpnaSora8CnUrV5XNOg+kyCz9edCyx4D5wXYcwZPVWz -8aDqquESrezPyjtfi4WRO4s/VD3HLZvOxzMrWAVYCDG9FxaOhF0QGuuG1F7F3GKV -v6prNyCl016kRl2j1UT+a7gLd8fA25A4C9E= +cnkxGDAWBgNVBAsTD1RCUyBJTlRFUk5FVCBDQTEdMBsGA1UEAxMUVEJTIFg1MDkg +Q0EgYnVzaW5lc3MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDB1PAU +qudCcz3tmyGcf+u6EkZqonKKHrV4gZYbvVkIRojmmlhfi/jwvpHvo8bqSt/9Rj5S +jhCDW0pcbI+IPPtD1Jy+CHNSfnMqVDy6CKQ3p5maTzCMG6ZT+XjnvcND5v+FtaiB +xk1iCX6uvt0jeUtdZvYbyytsSDE6c3Y5//wRxOF8tM1JxibwO3pyER26jbbN2gQz +m/EkdGjLdJ4svPk23WDAvQ6G0/z2LcAaJB+XLfqRwfQpHQvfKa1uTi8PivC8qtip +rmNQMMPMjxSK2azX8cKjjTDJiUKaCb4VHlJDWKEsCFRpgJAoAuX8f7Yfs1M4esGo +sWb3PGspK3O22uIlAgMBAAGjggF6MIIBdjAdBgNVHQ4EFgQUGgkEzPkJj7seoM7U +pBR/S9UjWM0wDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwGAYD +VR0gBBEwDzANBgsrBgEEAYDlNwIBATB7BgNVHR8EdDByMDigNqA0hjJodHRwOi8v +Y3JsLmNvbW9kb2NhLmNvbS9BZGRUcnVzdEV4dGVybmFsQ0FSb290LmNybDA2oDSg +MoYwaHR0cDovL2NybC5jb21vZG8ubmV0L0FkZFRydXN0RXh0ZXJuYWxDQVJvb3Qu +Y3JsMIGGBggrBgEFBQcBAQR6MHgwOwYIKwYBBQUHMAKGL2h0dHA6Ly9jcnQuY29t +b2RvY2EuY29tL0FkZFRydXN0VVROU2VydmVyQ0EuY3J0MDkGCCsGAQUFBzAChi1o +dHRwOi8vY3J0LmNvbW9kby5uZXQvQWRkVHJ1c3RVVE5TZXJ2ZXJDQS5jcnQwEQYJ +YIZIAYb4QgEBBAQDAgIEMA0GCSqGSIb3DQEBBQUAA4IBAQA7mqrMgk/MrE6QnbNA +h4nRCn2ti4bg4w2C3lB6bSvRPnYwuNw9Jb8vuKkNFzRDxNJXqVDZdfFW5CVQJuyd +nfAx83+wk+spzvFaE1KhFYfN9G9pQfXUfvDRoIcJgPEKUXL1wRiOG+IjU3VVI8pg +IgqHkr7ylln5i5zCiFAPuIJmYUSFg/gxH5xkCNcjJqqrHrHatJr6Qrrke93joupw +oU1njfAcZtYp6fbiK6u2b1pJqwkVBE8RsfLnPhRj+SFbpvjv8Od7o/ieJhFIYQNU +k2jX2u8qZnAiNw93LZW9lpYjtuvMXq8QQppENNja5b53q7UwI+lU7ZGjZ7quuESp +J6/5 -----END CERTIFICATE----- 2 s:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root - i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC + i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware -----BEGIN CERTIFICATE----- -MIIEZjCCA06gAwIBAgIQUSYKkxzif5zDpV954HKugjANBgkqhkiG9w0BAQUFADCB -kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +MIIETzCCAzegAwIBAgIQHM5EYpUZep1jUvnyI6m2mDANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw -IFNHQzAeFw0wNTA2MDcwODA5MTBaFw0xOTA2MjQxOTA2MzBaMG8xCzAJBgNVBAYT -AlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0 -ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB -IFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC39xoz5vIABC05 -4E5b7R+8bA/Ntfojts7emxEzl6QpTH2Tn71KvJPtAxrjj8/lbVBa1pcplFqAsEl6 -2y6V/bjKvzc4LR4+kUGtcFbH8E8/6DKedMrIkFTpxl8PeJ2aQDwOrGGqXhSPnoeh -alDc15pOrwWzpnGUnHGzUGAKxxOdOAeGAqjpqGkmGJCrTLBPI6s6T4TY386f4Wlv -u9dC12tE5Met7m1BX3JacQg3s3llpFmglDf3AC8NwpJy2tA4ctsUqEXEXSp9t7TW -xO6szRNEt8kr3UMAJfphuWlqWCMRt6czj1Z1WfXNKddGtworZbbTQm8Vsrh7++/p -XVPVNFonAgMBAAGjgdgwgdUwHwYDVR0jBBgwFoAUUzLRs89/+uDxoF2FTpLSnkUd -tE8wHQYDVR0OBBYEFK29mHo0tCb3+sQmVO8DveAky1QaMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MBEGCWCGSAGG+EIBAQQEAwIBAjAgBgNVHSUEGTAX -BgorBgEEAYI3CgMDBglghkgBhvhCBAEwPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDov -L2NybC51c2VydHJ1c3QuY29tL1VUTi1EQVRBQ29ycFNHQy5jcmwwDQYJKoZIhvcN -AQEFBQADggEBAMbuUxdoFLJRIh6QWA2U/b3xcOWGLcM2MY9USEbnLQg3vGwKYOEO -rVE04BKT6b64q7gmtOmWPSiPrmQH/uAB7MXjkesYoPF1ftsK5p+R26+udd8jkWjd -FwBaS/9kbHDrARrQkNnHptZt9hPk/7XJ0h4qy7ElQyZ42TCbTg0evmnv3+r+LbPM -+bDdtRTKkdSytaX7ARmjR3mfnYyVhzT4HziS2jamEfpr62vp3EV4FTkG101B5CHI -3C+H0be/SGB1pWLLJN47YaApIKa+xWycxOkKaSLvkTr6Jq/RW0GnOuL4OAdCq8Fb -+M5tug8EPzI0rNwEKNdwMBQmBsTkm5jVz3g= +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNMDUwNjA3MDgwOTEwWhcNMTkwNzA5MTgxOTIyWjBvMQswCQYD +VQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0 +IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5h +bCBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt/caM+by +AAQtOeBOW+0fvGwPzbX6I7bO3psRM5ekKUx9k5+9SryT7QMa44/P5W1QWtaXKZRa +gLBJetsulf24yr83OC0ePpFBrXBWx/BPP+gynnTKyJBU6cZfD3idmkA8Dqxhql4U +j56HoWpQ3NeaTq8Fs6ZxlJxxs1BgCscTnTgHhgKo6ahpJhiQq0ywTyOrOk+E2N/O +n+Fpb7vXQtdrROTHre5tQV9yWnEIN7N5ZaRZoJQ39wAvDcKSctrQOHLbFKhFxF0q +fbe01sTurM0TRLfJK91DACX6YblpalgjEbenM49WdVn1zSnXRrcKK2W200JvFbK4 +e/vv6V1T1TRaJwIDAQABo4G9MIG6MB8GA1UdIwQYMBaAFKFyXyYbKJhDlV0HN9WF +lp1L0sNFMB0GA1UdDgQWBBStvZh6NLQm9/rEJlTvA73gJMtUGjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAQIwRAYDVR0f +BD0wOzA5oDegNYYzaHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmly +c3QtSGFyZHdhcmUuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQByQhANOs4kClrwF8BW +onvUOGCSjRK52zYZgDXYNjDtmr5rJ6NyPFDNn+JxkLpjYetIFMTbSRe679Bt8m7a +gIAoQYFQtxMuyLnJegB2aEbQiIxh/tC21UcFF7ktdnDoTlA6w3pLuvunaI84Of3o +2YBrhzkTbCfaYk5JRlTpudW9DkUkHBsyx3nknPKnplkIGaK0jgn8E0n+SFabYaHk +I9LroYT/+JtLefh9lgBdAgVv0UPbzoGfuDsrk/Zh+UrgbLFpHoVnElhzbkh64Z0X +OGaJunQc68cCZu5HTn/aK7fBGMcVflRCXLVEQpU9PIAdGA8Ynvg684t8GMaKsRl1 +jIGZ -----END CERTIFICATE----- - 3 s:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC - i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN - DATACorp SGC + 3 s:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware + i:/C=US/ST=UT/L=Salt Lake City/O=The USERTRUST Network/OU=http://www.usertrust.com/CN=UTN-USERFirst-Hardware -----BEGIN CERTIFICATE----- -MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB -kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw -IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG -EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD -VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu -dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 -E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ -D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK -4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq -lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW -bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB -o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT -MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js -LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr -BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB -AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft -Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj -j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH -KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv -2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 -mfnGV/TJVTl4uix5yaaIK/QI +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe +MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v +d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh +cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn +0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ +M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a +MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd +oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI +DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy +oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 +dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy +bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF +BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli +CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE +CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t +3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS +KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== -----END CERTIFICATE----- diff --git a/lib-python/2.7/test/string_tests.py b/lib-python/2.7/test/string_tests.py --- a/lib-python/2.7/test/string_tests.py +++ b/lib-python/2.7/test/string_tests.py @@ -5,6 +5,7 @@ import unittest, string, sys, struct from test import test_support from UserList import UserList +import _testcapi class Sequence: def __init__(self, seq='wxyz'): self.seq = seq @@ -1113,6 +1114,23 @@ self.checkraises(TypeError, '%10.*f', '__mod__', ('foo', 42.)) self.checkraises(ValueError, '%10', '__mod__', (42,)) + width = int(_testcapi.PY_SSIZE_T_MAX + 1) + if width <= sys.maxint: + self.checkraises(OverflowError, '%*s', '__mod__', (width, '')) + prec = int(_testcapi.INT_MAX + 1) + if prec <= sys.maxint: + self.checkraises(OverflowError, '%.*f', '__mod__', (prec, 1. / 7)) + # Issue 15989 + width = int(1 << (_testcapi.PY_SSIZE_T_MAX.bit_length() + 1)) + if width <= sys.maxint: + self.checkraises(OverflowError, '%*s', '__mod__', (width, '')) + prec = int(_testcapi.UINT_MAX + 1) + if prec <= sys.maxint: + self.checkraises(OverflowError, '%.*f', '__mod__', (prec, 1. / 7)) + + class X(object): pass + self.checkraises(TypeError, 'abc', '__mod__', X()) + def test_floatformatting(self): # float formatting for prec in xrange(100): diff --git a/lib-python/2.7/test/subprocessdata/sigchild_ignore.py b/lib-python/2.7/test/subprocessdata/sigchild_ignore.py --- a/lib-python/2.7/test/subprocessdata/sigchild_ignore.py +++ b/lib-python/2.7/test/subprocessdata/sigchild_ignore.py @@ -1,6 +1,15 @@ -import signal, subprocess, sys +import signal, subprocess, sys, time # On Linux this causes os.waitpid to fail with OSError as the OS has already # reaped our child process. The wait() passing the OSError on to the caller # and causing us to exit with an error is what we are testing against. signal.signal(signal.SIGCHLD, signal.SIG_IGN) subprocess.Popen([sys.executable, '-c', 'print("albatross")']).wait() +# Also ensure poll() handles an errno.ECHILD appropriately. +p = subprocess.Popen([sys.executable, '-c', 'print("albatross")']) +num_polls = 0 +while p.poll() is None: + # Waiting for the process to finish. + time.sleep(0.01) # Avoid being a CPU busy loop. + num_polls += 1 + if num_polls > 3000: + raise RuntimeError('poll should have returned 0 within 30 seconds') diff --git a/lib-python/2.7/test/test_StringIO.py b/lib-python/2.7/test/test_StringIO.py --- a/lib-python/2.7/test/test_StringIO.py +++ b/lib-python/2.7/test/test_StringIO.py @@ -5,6 +5,7 @@ import cStringIO import types import array +import sys from test import test_support @@ -27,6 +28,8 @@ eq = self.assertEqual self.assertRaises(TypeError, self._fp.seek) eq(self._fp.read(10), self._line[:10]) + eq(self._fp.read(0), '') + eq(self._fp.readline(0), '') eq(self._fp.readline(), self._line[10:] + '\n') eq(len(self._fp.readlines(60)), 2) self._fp.seek(0) @@ -105,6 +108,45 @@ self._fp.close() self.assertRaises(ValueError, self._fp.getvalue) + @test_support.bigmemtest(test_support._2G + 2**26, memuse=2.001) + def test_reads_from_large_stream(self, size): + linesize = 2**26 # 64 MiB + lines = ['x' * (linesize - 1) + '\n'] * (size // linesize) + \ + ['y' * (size % linesize)] + f = self.MODULE.StringIO(''.join(lines)) + for i, expected in enumerate(lines): + line = f.read(len(expected)) + self.assertEqual(len(line), len(expected)) + self.assertEqual(line, expected) + self.assertEqual(f.read(), '') + f.seek(0) + for i, expected in enumerate(lines): + line = f.readline() + self.assertEqual(len(line), len(expected)) + self.assertEqual(line, expected) + self.assertEqual(f.readline(), '') + f.seek(0) + self.assertEqual(f.readlines(), lines) + self.assertEqual(f.readlines(), []) + f.seek(0) + self.assertEqual(f.readlines(size), lines) + self.assertEqual(f.readlines(), []) + + # In worst case cStringIO requires 2 + 1 + 1/2 + 1/2**2 + ... = 4 + # bytes per input character. + @test_support.bigmemtest(test_support._2G, memuse=4) + def test_writes_to_large_stream(self, size): + s = 'x' * 2**26 # 64 MiB + f = self.MODULE.StringIO() + n = size + while n > len(s): + f.write(s) + n -= len(s) + s = None + f.write('x' * n) + self.assertEqual(len(f.getvalue()), size) + + class TestStringIO(TestGenericStringIO): MODULE = StringIO diff --git a/lib-python/2.7/test/test__osx_support.py b/lib-python/2.7/test/test__osx_support.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/test__osx_support.py @@ -0,0 +1,279 @@ +""" +Test suite for _osx_support: shared OS X support functions. +""" + +import os +import platform +import shutil +import stat +import sys +import unittest + +import test.test_support + +import _osx_support + + at unittest.skipUnless(sys.platform.startswith("darwin"), "requires OS X") +class Test_OSXSupport(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + self.prog_name = 'bogus_program_xxxx' + self.temp_path_dir = os.path.abspath(os.getcwd()) + self.env = test.test_support.EnvironmentVarGuard() + self.addCleanup(self.env.__exit__) + for cv in ('CFLAGS', 'LDFLAGS', 'CPPFLAGS', + 'BASECFLAGS', 'BLDSHARED', 'LDSHARED', 'CC', + 'CXX', 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS', + 'PY_CORE_CFLAGS'): + if cv in self.env: + self.env.unset(cv) + + def add_expected_saved_initial_values(self, config_vars, expected_vars): + # Ensure that the initial values for all modified config vars + # are also saved with modified keys. + expected_vars.update(('_OSX_SUPPORT_INITIAL_'+ k, + config_vars[k]) for k in config_vars + if config_vars[k] != expected_vars[k]) + + def test__find_executable(self): + if self.env['PATH']: + self.env['PATH'] = self.env['PATH'] + ':' + self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir) + test.test_support.unlink(self.prog_name) + self.assertIsNone(_osx_support._find_executable(self.prog_name)) + self.addCleanup(test.test_support.unlink, self.prog_name) + with open(self.prog_name, 'w') as f: + f.write("#!/bin/sh\n/bin/echo OK\n") + os.chmod(self.prog_name, stat.S_IRWXU) + self.assertEqual(self.prog_name, + _osx_support._find_executable(self.prog_name)) + + def test__read_output(self): + if self.env['PATH']: + self.env['PATH'] = self.env['PATH'] + ':' + self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir) + test.test_support.unlink(self.prog_name) + self.addCleanup(test.test_support.unlink, self.prog_name) + with open(self.prog_name, 'w') as f: + f.write("#!/bin/sh\n/bin/echo ExpectedOutput\n") + os.chmod(self.prog_name, stat.S_IRWXU) + self.assertEqual('ExpectedOutput', + _osx_support._read_output(self.prog_name)) + + def test__find_build_tool(self): + out = _osx_support._find_build_tool('cc') + self.assertTrue(os.path.isfile(out), + 'cc not found - check xcode-select') + + def test__get_system_version(self): + self.assertTrue(platform.mac_ver()[0].startswith( + _osx_support._get_system_version())) + + def test__remove_original_values(self): + config_vars = { + 'CC': 'gcc-test -pthreads', + } + expected_vars = { + 'CC': 'clang -pthreads', + } + cv = 'CC' + newvalue = 'clang -pthreads' + _osx_support._save_modified_value(config_vars, cv, newvalue) + self.assertNotEqual(expected_vars, config_vars) + _osx_support._remove_original_values(config_vars) + self.assertEqual(expected_vars, config_vars) + + def test__save_modified_value(self): + config_vars = { + 'CC': 'gcc-test -pthreads', + } + expected_vars = { + 'CC': 'clang -pthreads', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + cv = 'CC' + newvalue = 'clang -pthreads' + _osx_support._save_modified_value(config_vars, cv, newvalue) + self.assertEqual(expected_vars, config_vars) + + def test__save_modified_value_unchanged(self): + config_vars = { + 'CC': 'gcc-test -pthreads', + } + expected_vars = config_vars.copy() + cv = 'CC' + newvalue = 'gcc-test -pthreads' + _osx_support._save_modified_value(config_vars, cv, newvalue) + self.assertEqual(expected_vars, config_vars) + + def test__supports_universal_builds(self): + import platform + self.assertEqual(platform.mac_ver()[0].split('.') >= ['10', '4'], + _osx_support._supports_universal_builds()) + + def test__find_appropriate_compiler(self): + compilers = ( + ('gcc-test', 'i686-apple-darwin11-llvm-gcc-4.2'), + ('clang', 'clang version 3.1'), + ) + config_vars = { + 'CC': 'gcc-test -pthreads', + 'CXX': 'cc++-test', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-test -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-test -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + expected_vars = { + 'CC': 'clang -pthreads', + 'CXX': 'clang++', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'clang -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'clang -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + suffix = (':' + self.env['PATH']) if self.env['PATH'] else '' + self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix + for c_name, c_output in compilers: + test.test_support.unlink(c_name) + self.addCleanup(test.test_support.unlink, c_name) + with open(c_name, 'w') as f: + f.write("#!/bin/sh\n/bin/echo " + c_output) + os.chmod(c_name, stat.S_IRWXU) + self.assertEqual(expected_vars, + _osx_support._find_appropriate_compiler( + config_vars)) + + def test__remove_universal_flags(self): + config_vars = { + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + expected_vars = { + 'CFLAGS': '-fno-strict-aliasing -g -O3 ', + 'LDFLAGS': ' -g', + 'CPPFLAGS': '-I. ', + 'BLDSHARED': 'gcc-4.0 -bundle -g', + 'LDSHARED': 'gcc-4.0 -bundle -g', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + self.assertEqual(expected_vars, + _osx_support._remove_universal_flags( + config_vars)) + + def test__remove_unsupported_archs(self): + config_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + expected_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch i386 ', + 'LDFLAGS': ' -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + suffix = (':' + self.env['PATH']) if self.env['PATH'] else '' + self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix + c_name = 'clang' + test.test_support.unlink(c_name) + self.addCleanup(test.test_support.unlink, c_name) + # exit status 255 means no PPC support in this compiler chain + with open(c_name, 'w') as f: + f.write("#!/bin/sh\nexit 255") + os.chmod(c_name, stat.S_IRWXU) + self.assertEqual(expected_vars, + _osx_support._remove_unsupported_archs( + config_vars)) + + def test__override_all_archs(self): + self.env['ARCHFLAGS'] = '-arch x86_64' + config_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g', + } + expected_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch x86_64', + 'LDFLAGS': ' -g -arch x86_64', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -g -arch x86_64', + 'LDSHARED': 'gcc-4.0 -bundle -isysroot ' + '/Developer/SDKs/MacOSX10.4u.sdk -g -arch x86_64', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + self.assertEqual(expected_vars, + _osx_support._override_all_archs( + config_vars)) + + def test__check_for_unavailable_sdk(self): + config_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.1.sdk', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.1.sdk', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.1.sdk -g', + } + expected_vars = { + 'CC': 'clang', + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ' + ' ', + 'LDFLAGS': '-arch ppc -arch i386 -g', + 'CPPFLAGS': '-I. ', + 'BLDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 -g', + 'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 ' + ' -g', + } + self.add_expected_saved_initial_values(config_vars, expected_vars) + + self.assertEqual(expected_vars, + _osx_support._check_for_unavailable_sdk( + config_vars)) + + def test_get_platform_osx(self): + # Note, get_platform_osx is currently tested more extensively + # indirectly by test_sysconfig and test_distutils + config_vars = { + 'CFLAGS': '-fno-strict-aliasing -g -O3 -arch ppc -arch i386 ' + '-isysroot /Developer/SDKs/MacOSX10.1.sdk', + 'MACOSX_DEPLOYMENT_TARGET': '10.6', + } + result = _osx_support.get_platform_osx(config_vars, ' ', ' ', ' ') + self.assertEqual(('macosx', '10.6', 'fat'), result) + +def test_main(): + if sys.platform == 'darwin': + test.test_support.run_unittest(Test_OSXSupport) + +if __name__ == "__main__": + test_main() diff --git a/lib-python/2.7/test/test_aifc.py b/lib-python/2.7/test/test_aifc.py --- a/lib-python/2.7/test/test_aifc.py +++ b/lib-python/2.7/test/test_aifc.py @@ -106,6 +106,13 @@ self.assertEqual(testfile.closed, False) f.close() self.assertEqual(testfile.closed, True) + testfile = open(TESTFN, 'wb') + fout = aifc.open(testfile, 'wb') + self.assertFalse(testfile.closed) + with self.assertRaises(aifc.Error): + fout.close() + self.assertTrue(testfile.closed) + fout.close() # do nothing class AIFCLowLevelTest(unittest.TestCase): diff --git a/lib-python/2.7/test/test_argparse.py b/lib-python/2.7/test/test_argparse.py --- a/lib-python/2.7/test/test_argparse.py +++ b/lib-python/2.7/test/test_argparse.py @@ -1374,6 +1374,7 @@ ('X @hello', NS(a=None, x='X', y=['hello world!'])), ('-a B @recursive Y Z', NS(a='A', x='hello world!', y=['Y', 'Z'])), ('X @recursive Z -a B', NS(a='B', x='X', y=['hello world!', 'Z'])), + (["-a", "", "X", "Y"], NS(a='', x='X', y=['Y'])), ] @@ -1466,6 +1467,22 @@ ('readonly', NS(x=None, spam=RFile('readonly'))), ] +class TestFileTypeDefaults(TempDirMixin, ParserTestCase): + """Test that a file is not created unless the default is needed""" + def setUp(self): + super(TestFileTypeDefaults, self).setUp() + file = open(os.path.join(self.temp_dir, 'good'), 'w') + file.write('good') + file.close() + + argument_signatures = [ + Sig('-c', type=argparse.FileType('r'), default='no-file.txt'), + ] + # should provoke no such file error + failures = [''] + # should not provoke error because default file is created + successes = [('-c good', NS(c=RFile('good')))] + class TestFileTypeRB(TempDirMixin, ParserTestCase): """Test the FileType option/argument type for reading files""" @@ -1763,6 +1780,14 @@ parser2.add_argument('-y', choices='123', help='y help') parser2.add_argument('z', type=complex, nargs='*', help='z help') + # add third sub-parser + parser3_kwargs = dict(description='3 description') + if subparser_help: + parser3_kwargs['help'] = '3 help' + parser3 = subparsers.add_parser('3', **parser3_kwargs) + parser3.add_argument('t', type=int, help='t help') + parser3.add_argument('u', nargs='...', help='u help') + # return the main parser return parser @@ -1792,6 +1817,10 @@ self.parser.parse_args('--foo 0.125 1 c'.split()), NS(foo=True, bar=0.125, w=None, x='c'), ) + self.assertEqual( + self.parser.parse_args('-1.5 3 11 -- a --foo 7 -- b'.split()), + NS(foo=False, bar=-1.5, t=11, u=['a', '--foo', '7', '--', 'b']), + ) def test_parse_known_args(self): self.assertEqual( @@ -1826,15 +1855,15 @@ def test_help(self): self.assertEqual(self.parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + 'usage: PROG [-h] [--foo] bar {1,2,3} ...\n') self.assertEqual(self.parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... + usage: PROG [-h] [--foo] bar {1,2,3} ... main description positional arguments: bar bar help - {1,2} command help + {1,2,3} command help optional arguments: -h, --help show this help message and exit @@ -1845,15 +1874,15 @@ # Make sure - is still used for help if it is a non-first prefix char parser = self._get_parser(prefix_chars='+:-') self.assertEqual(parser.format_usage(), - 'usage: PROG [-h] [++foo] bar {1,2} ...\n') + 'usage: PROG [-h] [++foo] bar {1,2,3} ...\n') self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [++foo] bar {1,2} ... + usage: PROG [-h] [++foo] bar {1,2,3} ... main description positional arguments: bar bar help - {1,2} command help + {1,2,3} command help optional arguments: -h, --help show this help message and exit @@ -1864,15 +1893,15 @@ def test_help_alternate_prefix_chars(self): parser = self._get_parser(prefix_chars='+:/') self.assertEqual(parser.format_usage(), - 'usage: PROG [+h] [++foo] bar {1,2} ...\n') + 'usage: PROG [+h] [++foo] bar {1,2,3} ...\n') self.assertEqual(parser.format_help(), textwrap.dedent('''\ - usage: PROG [+h] [++foo] bar {1,2} ... + usage: PROG [+h] [++foo] bar {1,2,3} ... main description positional arguments: bar bar help - {1,2} command help + {1,2,3} command help optional arguments: +h, ++help show this help message and exit @@ -1881,18 +1910,19 @@ def test_parser_command_help(self): self.assertEqual(self.command_help_parser.format_usage(), - 'usage: PROG [-h] [--foo] bar {1,2} ...\n') + 'usage: PROG [-h] [--foo] bar {1,2,3} ...\n') self.assertEqual(self.command_help_parser.format_help(), textwrap.dedent('''\ - usage: PROG [-h] [--foo] bar {1,2} ... + usage: PROG [-h] [--foo] bar {1,2,3} ... main description positional arguments: bar bar help - {1,2} command help + {1,2,3} command help 1 1 help 2 2 help + 3 3 help optional arguments: -h, --help show this help message and exit @@ -4418,12 +4448,95 @@ else: self.fail() +# ================================================ +# Check that the type function is called only once +# ================================================ + +class TestTypeFunctionCallOnlyOnce(TestCase): + + def test_type_function_call_only_once(self): + def spam(string_to_convert): + self.assertEqual(string_to_convert, 'spam!') + return 'foo_converted' + + parser = argparse.ArgumentParser() + parser.add_argument('--foo', type=spam, default='bar') + args = parser.parse_args('--foo spam!'.split()) + self.assertEqual(NS(foo='foo_converted'), args) + +# ================================================================== +# Check semantics regarding the default argument and type conversion +# ================================================================== + +class TestTypeFunctionCalledOnDefault(TestCase): + + def test_type_function_call_with_non_string_default(self): + def spam(int_to_convert): + self.assertEqual(int_to_convert, 0) + return 'foo_converted' + + parser = argparse.ArgumentParser() + parser.add_argument('--foo', type=spam, default=0) + args = parser.parse_args([]) + # foo should *not* be converted because its default is not a string. + self.assertEqual(NS(foo=0), args) + + def test_type_function_call_with_string_default(self): + def spam(int_to_convert): + return 'foo_converted' + + parser = argparse.ArgumentParser() + parser.add_argument('--foo', type=spam, default='0') + args = parser.parse_args([]) + # foo is converted because its default is a string. + self.assertEqual(NS(foo='foo_converted'), args) + + def test_no_double_type_conversion_of_default(self): + def extend(str_to_convert): + return str_to_convert + '*' + + parser = argparse.ArgumentParser() + parser.add_argument('--test', type=extend, default='*') + args = parser.parse_args([]) + # The test argument will be two stars, one coming from the default + # value and one coming from the type conversion being called exactly + # once. + self.assertEqual(NS(test='**'), args) + + def test_issue_15906(self): + # Issue #15906: When action='append', type=str, default=[] are + # providing, the dest value was the string representation "[]" when it + # should have been an empty list. + parser = argparse.ArgumentParser() + parser.add_argument('--test', dest='test', type=str, + default=[], action='append') + args = parser.parse_args([]) + self.assertEqual(args.test, []) + # ====================== # parse_known_args tests # ====================== class TestParseKnownArgs(TestCase): + def test_arguments_tuple(self): + parser = argparse.ArgumentParser() + parser.parse_args(()) + + def test_arguments_list(self): + parser = argparse.ArgumentParser() + parser.parse_args([]) + + def test_arguments_tuple_positional(self): + parser = argparse.ArgumentParser() + parser.add_argument('x') + parser.parse_args(('x',)) + + def test_arguments_list_positional(self): + parser = argparse.ArgumentParser() + parser.add_argument('x') + parser.parse_args(['x']) + def test_optionals(self): parser = argparse.ArgumentParser() parser.add_argument('--foo') diff --git a/lib-python/2.7/test/test_array.py b/lib-python/2.7/test/test_array.py --- a/lib-python/2.7/test/test_array.py +++ b/lib-python/2.7/test/test_array.py @@ -985,6 +985,19 @@ upper = long(pow(2, a.itemsize * 8)) - 1L self.check_overflow(lower, upper) + @test_support.cpython_only + def test_sizeof_with_buffer(self): + a = array.array(self.typecode, self.example) + basesize = test_support.calcvobjsize('4P') + buffer_size = a.buffer_info()[1] * a.itemsize + test_support.check_sizeof(self, a, basesize + buffer_size) + + @test_support.cpython_only + def test_sizeof_without_buffer(self): + a = array.array(self.typecode) + basesize = test_support.calcvobjsize('4P') + test_support.check_sizeof(self, a, basesize) + class ByteTest(SignedNumberTest): typecode = 'b' diff --git a/lib-python/2.7/test/test_ast.py b/lib-python/2.7/test/test_ast.py --- a/lib-python/2.7/test/test_ast.py +++ b/lib-python/2.7/test/test_ast.py @@ -231,6 +231,12 @@ im = ast.parse("from . import y").body[0] self.assertIsNone(im.module) + def test_non_interned_future_from_ast(self): + mod = ast.parse("from __future__ import division") + self.assertIsInstance(mod.body[0], ast.ImportFrom) + mod.body[0].module = " __future__ ".strip() + compile(mod, "", "exec") + def test_base_classes(self): self.assertTrue(issubclass(ast.For, ast.stmt)) self.assertTrue(issubclass(ast.Name, ast.expr)) diff --git a/lib-python/2.7/test/test_asyncore.py b/lib-python/2.7/test/test_asyncore.py --- a/lib-python/2.7/test/test_asyncore.py +++ b/lib-python/2.7/test/test_asyncore.py @@ -7,6 +7,7 @@ import time import warnings import errno +import struct from test import test_support from test.test_support import TESTFN, run_unittest, unlink @@ -483,8 +484,9 @@ return self.socket.getsockname()[:2] def handle_accept(self): - sock, addr = self.accept() - self.handler(sock) + pair = self.accept() + if pair is not None: + self.handler(pair[0]) def handle_error(self): raise @@ -703,6 +705,27 @@ finally: sock.close() + @unittest.skipUnless(threading, 'Threading required for this test.') + @test_support.reap_threads + def test_quick_connect(self): + # see: http://bugs.python.org/issue10340 + server = TCPServer() + t = threading.Thread(target=lambda: asyncore.loop(timeout=0.1, count=500)) + t.start() + self.addCleanup(t.join) + + for x in xrange(20): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(.2) + s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, + struct.pack('ii', 1, 0)) + try: + s.connect(server.address) + except socket.error: + pass + finally: + s.close() + class TestAPI_UseSelect(BaseTestAPI): use_poll = False diff --git a/lib-python/2.7/test/test_audioop.py b/lib-python/2.7/test/test_audioop.py --- a/lib-python/2.7/test/test_audioop.py +++ b/lib-python/2.7/test/test_audioop.py @@ -1,25 +1,33 @@ import audioop +import sys import unittest +import struct from test.test_support import run_unittest -endian = 'big' if audioop.getsample('\0\1', 2, 0) == 1 else 'little' -def gendata1(): - return '\0\1\2' +formats = { + 1: 'b', + 2: 'h', + 4: 'i', +} -def gendata2(): - if endian == 'big': - return '\0\0\0\1\0\2' - else: - return '\0\0\1\0\2\0' +def pack(width, data): + return struct.pack('=%d%s' % (len(data), formats[width]), *data) -def gendata4(): - if endian == 'big': - return '\0\0\0\0\0\0\0\1\0\0\0\2' - else: - return '\0\0\0\0\1\0\0\0\2\0\0\0' +packs = { + 1: lambda *data: pack(1, data), + 2: lambda *data: pack(2, data), + 4: lambda *data: pack(4, data), +} +maxvalues = {w: (1 << (8 * w - 1)) - 1 for w in (1, 2, 4)} +minvalues = {w: -1 << (8 * w - 1) for w in (1, 2, 4)} -data = [gendata1(), gendata2(), gendata4()] +datas = { + 1: b'\x00\x12\x45\xbb\x7f\x80\xff', + 2: packs[2](0, 0x1234, 0x4567, -0x4567, 0x7fff, -0x8000, -1), + 4: packs[4](0, 0x12345678, 0x456789ab, -0x456789ab, + 0x7fffffff, -0x80000000, -1), +} INVALID_DATA = [ (b'abc', 0), @@ -31,164 +39,315 @@ class TestAudioop(unittest.TestCase): def test_max(self): - self.assertEqual(audioop.max(data[0], 1), 2) - self.assertEqual(audioop.max(data[1], 2), 2) - self.assertEqual(audioop.max(data[2], 4), 2) + for w in 1, 2, 4: + self.assertEqual(audioop.max(b'', w), 0) + p = packs[w] + self.assertEqual(audioop.max(p(5), w), 5) + self.assertEqual(audioop.max(p(5, -8, -1), w), 8) + self.assertEqual(audioop.max(p(maxvalues[w]), w), maxvalues[w]) + self.assertEqual(audioop.max(p(minvalues[w]), w), -minvalues[w]) + self.assertEqual(audioop.max(datas[w], w), -minvalues[w]) def test_minmax(self): - self.assertEqual(audioop.minmax(data[0], 1), (0, 2)) - self.assertEqual(audioop.minmax(data[1], 2), (0, 2)) - self.assertEqual(audioop.minmax(data[2], 4), (0, 2)) + for w in 1, 2, 4: + self.assertEqual(audioop.minmax(b'', w), + (0x7fffffff, -0x80000000)) + p = packs[w] + self.assertEqual(audioop.minmax(p(5), w), (5, 5)) + self.assertEqual(audioop.minmax(p(5, -8, -1), w), (-8, 5)) + self.assertEqual(audioop.minmax(p(maxvalues[w]), w), + (maxvalues[w], maxvalues[w])) + self.assertEqual(audioop.minmax(p(minvalues[w]), w), + (minvalues[w], minvalues[w])) + self.assertEqual(audioop.minmax(datas[w], w), + (minvalues[w], maxvalues[w])) def test_maxpp(self): - self.assertEqual(audioop.maxpp(data[0], 1), 0) - self.assertEqual(audioop.maxpp(data[1], 2), 0) - self.assertEqual(audioop.maxpp(data[2], 4), 0) + for w in 1, 2, 4: + self.assertEqual(audioop.maxpp(b'', w), 0) + self.assertEqual(audioop.maxpp(packs[w](*range(100)), w), 0) + self.assertEqual(audioop.maxpp(packs[w](9, 10, 5, 5, 0, 1), w), 10) + self.assertEqual(audioop.maxpp(datas[w], w), + maxvalues[w] - minvalues[w]) def test_avg(self): - self.assertEqual(audioop.avg(data[0], 1), 1) - self.assertEqual(audioop.avg(data[1], 2), 1) - self.assertEqual(audioop.avg(data[2], 4), 1) + for w in 1, 2, 4: + self.assertEqual(audioop.avg(b'', w), 0) + p = packs[w] + self.assertEqual(audioop.avg(p(5), w), 5) + self .assertEqual(audioop.avg(p(5, 8), w), 6) + self.assertEqual(audioop.avg(p(5, -8), w), -2) + self.assertEqual(audioop.avg(p(maxvalues[w], maxvalues[w]), w), + maxvalues[w]) + self.assertEqual(audioop.avg(p(minvalues[w], minvalues[w]), w), + minvalues[w]) + self.assertEqual(audioop.avg(packs[4](0x50000000, 0x70000000), 4), + 0x60000000) + self.assertEqual(audioop.avg(packs[4](-0x50000000, -0x70000000), 4), + -0x60000000) def test_avgpp(self): - self.assertEqual(audioop.avgpp(data[0], 1), 0) - self.assertEqual(audioop.avgpp(data[1], 2), 0) - self.assertEqual(audioop.avgpp(data[2], 4), 0) + for w in 1, 2, 4: + self.assertEqual(audioop.avgpp(b'', w), 0) + self.assertEqual(audioop.avgpp(packs[w](*range(100)), w), 0) + self.assertEqual(audioop.avgpp(packs[w](9, 10, 5, 5, 0, 1), w), 10) + self.assertEqual(audioop.avgpp(datas[1], 1), 196) + self.assertEqual(audioop.avgpp(datas[2], 2), 50534) + self.assertEqual(audioop.avgpp(datas[4], 4), 3311897002) def test_rms(self): - self.assertEqual(audioop.rms(data[0], 1), 1) - self.assertEqual(audioop.rms(data[1], 2), 1) - self.assertEqual(audioop.rms(data[2], 4), 1) + for w in 1, 2, 4: + self.assertEqual(audioop.rms(b'', w), 0) + p = packs[w] + self.assertEqual(audioop.rms(p(*range(100)), w), 57) + self.assertAlmostEqual(audioop.rms(p(maxvalues[w]) * 5, w), + maxvalues[w], delta=1) + self.assertAlmostEqual(audioop.rms(p(minvalues[w]) * 5, w), + -minvalues[w], delta=1) + self.assertEqual(audioop.rms(datas[1], 1), 77) + self.assertEqual(audioop.rms(datas[2], 2), 20001) + self.assertEqual(audioop.rms(datas[4], 4), 1310854152) def test_cross(self): - self.assertEqual(audioop.cross(data[0], 1), 0) - self.assertEqual(audioop.cross(data[1], 2), 0) - self.assertEqual(audioop.cross(data[2], 4), 0) + for w in 1, 2, 4: + self.assertEqual(audioop.cross(b'', w), -1) + p = packs[w] + self.assertEqual(audioop.cross(p(0, 1, 2), w), 0) + self.assertEqual(audioop.cross(p(1, 2, -3, -4), w), 1) + self.assertEqual(audioop.cross(p(-1, -2, 3, 4), w), 1) + self.assertEqual(audioop.cross(p(0, minvalues[w]), w), 1) + self.assertEqual(audioop.cross(p(minvalues[w], maxvalues[w]), w), 1) def test_add(self): - data2 = [] - for d in data: - str = '' - for s in d: - str = str + chr(ord(s)*2) - data2.append(str) - self.assertEqual(audioop.add(data[0], data[0], 1), data2[0]) - self.assertEqual(audioop.add(data[1], data[1], 2), data2[1]) - self.assertEqual(audioop.add(data[2], data[2], 4), data2[2]) + for w in 1, 2, 4: + self.assertEqual(audioop.add(b'', b'', w), b'') + self.assertEqual(audioop.add(datas[w], b'\0' * len(datas[w]), w), + datas[w]) + self.assertEqual(audioop.add(datas[1], datas[1], 1), + b'\x00\x24\x7f\x80\x7f\x80\xfe') + self.assertEqual(audioop.add(datas[2], datas[2], 2), + packs[2](0, 0x2468, 0x7fff, -0x8000, 0x7fff, -0x8000, -2)) + self.assertEqual(audioop.add(datas[4], datas[4], 4), + packs[4](0, 0x2468acf0, 0x7fffffff, -0x80000000, + 0x7fffffff, -0x80000000, -2)) def test_bias(self): - # Note: this test assumes that avg() works - d1 = audioop.bias(data[0], 1, 100) - d2 = audioop.bias(data[1], 2, 100) - d4 = audioop.bias(data[2], 4, 100) - self.assertEqual(audioop.avg(d1, 1), 101) - self.assertEqual(audioop.avg(d2, 2), 101) - self.assertEqual(audioop.avg(d4, 4), 101) + for w in 1, 2, 4: + for bias in 0, 1, -1, 127, -128, 0x7fffffff, -0x80000000: + self.assertEqual(audioop.bias(b'', w, bias), b'') + self.assertEqual(audioop.bias(datas[1], 1, 1), + b'\x01\x13\x46\xbc\x80\x81\x00') + self.assertEqual(audioop.bias(datas[1], 1, -1), + b'\xff\x11\x44\xba\x7e\x7f\xfe') + self.assertEqual(audioop.bias(datas[1], 1, 0x7fffffff), + b'\xff\x11\x44\xba\x7e\x7f\xfe') + self.assertEqual(audioop.bias(datas[1], 1, -0x80000000), + datas[1]) + self.assertEqual(audioop.bias(datas[2], 2, 1), + packs[2](1, 0x1235, 0x4568, -0x4566, -0x8000, -0x7fff, 0)) + self.assertEqual(audioop.bias(datas[2], 2, -1), + packs[2](-1, 0x1233, 0x4566, -0x4568, 0x7ffe, 0x7fff, -2)) + self.assertEqual(audioop.bias(datas[2], 2, 0x7fffffff), + packs[2](-1, 0x1233, 0x4566, -0x4568, 0x7ffe, 0x7fff, -2)) + self.assertEqual(audioop.bias(datas[2], 2, -0x80000000), + datas[2]) + self.assertEqual(audioop.bias(datas[4], 4, 1), + packs[4](1, 0x12345679, 0x456789ac, -0x456789aa, + -0x80000000, -0x7fffffff, 0)) + self.assertEqual(audioop.bias(datas[4], 4, -1), + packs[4](-1, 0x12345677, 0x456789aa, -0x456789ac, + 0x7ffffffe, 0x7fffffff, -2)) + self.assertEqual(audioop.bias(datas[4], 4, 0x7fffffff), + packs[4](0x7fffffff, -0x6dcba989, -0x3a987656, 0x3a987654, + -2, -1, 0x7ffffffe)) + self.assertEqual(audioop.bias(datas[4], 4, -0x80000000), + packs[4](-0x80000000, -0x6dcba988, -0x3a987655, 0x3a987655, + -1, 0, 0x7fffffff)) def test_lin2lin(self): - # too simple: we test only the size - for d1 in data: - for d2 in data: - got = len(d1)//3 - wtd = len(d2)//3 - self.assertEqual(len(audioop.lin2lin(d1, got, wtd)), len(d2)) + for w in 1, 2, 4: + self.assertEqual(audioop.lin2lin(datas[w], w, w), datas[w]) + + self.assertEqual(audioop.lin2lin(datas[1], 1, 2), + packs[2](0, 0x1200, 0x4500, -0x4500, 0x7f00, -0x8000, -0x100)) + self.assertEqual(audioop.lin2lin(datas[1], 1, 4), + packs[4](0, 0x12000000, 0x45000000, -0x45000000, + 0x7f000000, -0x80000000, -0x1000000)) + self.assertEqual(audioop.lin2lin(datas[2], 2, 1), + b'\x00\x12\x45\xba\x7f\x80\xff') + self.assertEqual(audioop.lin2lin(datas[2], 2, 4), + packs[4](0, 0x12340000, 0x45670000, -0x45670000, + 0x7fff0000, -0x80000000, -0x10000)) + self.assertEqual(audioop.lin2lin(datas[4], 4, 1), + b'\x00\x12\x45\xba\x7f\x80\xff') + self.assertEqual(audioop.lin2lin(datas[4], 4, 2), + packs[2](0, 0x1234, 0x4567, -0x4568, 0x7fff, -0x8000, -1)) def test_adpcm2lin(self): + self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 1, None), + (b'\x00\x00\x00\xff\x00\xff', (-179, 40))) + self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 2, None), + (packs[2](0, 0xb, 0x29, -0x16, 0x72, -0xb3), (-179, 40))) + self.assertEqual(audioop.adpcm2lin(b'\x07\x7f\x7f', 4, None), + (packs[4](0, 0xb0000, 0x290000, -0x160000, 0x720000, + -0xb30000), (-179, 40))) + # Very cursory test - self.assertEqual(audioop.adpcm2lin(b'\0\0', 1, None), (b'\0' * 4, (0,0))) - self.assertEqual(audioop.adpcm2lin(b'\0\0', 2, None), (b'\0' * 8, (0,0))) - self.assertEqual(audioop.adpcm2lin(b'\0\0', 4, None), (b'\0' * 16, (0,0))) + for w in 1, 2, 4: + self.assertEqual(audioop.adpcm2lin(b'\0' * 5, w, None), + (b'\0' * w * 10, (0, 0))) def test_lin2adpcm(self): + self.assertEqual(audioop.lin2adpcm(datas[1], 1, None), + (b'\x07\x7f\x7f', (-221, 39))) + self.assertEqual(audioop.lin2adpcm(datas[2], 2, None), + (b'\x07\x7f\x7f', (31, 39))) + self.assertEqual(audioop.lin2adpcm(datas[4], 4, None), + (b'\x07\x7f\x7f', (31, 39))) + # Very cursory test - self.assertEqual(audioop.lin2adpcm('\0\0\0\0', 1, None), ('\0\0', (0,0))) + for w in 1, 2, 4: + self.assertEqual(audioop.lin2adpcm(b'\0' * w * 10, w, None), + (b'\0' * 5, (0, 0))) def test_lin2alaw(self): - self.assertEqual(audioop.lin2alaw(data[0], 1), '\xd5\xc5\xf5') - self.assertEqual(audioop.lin2alaw(data[1], 2), '\xd5\xd5\xd5') - self.assertEqual(audioop.lin2alaw(data[2], 4), '\xd5\xd5\xd5') + self.assertEqual(audioop.lin2alaw(datas[1], 1), + b'\xd5\x87\xa4\x24\xaa\x2a\x5a') + self.assertEqual(audioop.lin2alaw(datas[2], 2), + b'\xd5\x87\xa4\x24\xaa\x2a\x55') + self.assertEqual(audioop.lin2alaw(datas[4], 4), + b'\xd5\x87\xa4\x24\xaa\x2a\x55') def test_alaw2lin(self): - # Cursory - d = audioop.lin2alaw(data[0], 1) - self.assertEqual(audioop.alaw2lin(d, 1), data[0]) - if endian == 'big': - self.assertEqual(audioop.alaw2lin(d, 2), - b'\x00\x08\x01\x08\x02\x10') - self.assertEqual(audioop.alaw2lin(d, 4), - b'\x00\x08\x00\x00\x01\x08\x00\x00\x02\x10\x00\x00') - else: - self.assertEqual(audioop.alaw2lin(d, 2), - b'\x08\x00\x08\x01\x10\x02') - self.assertEqual(audioop.alaw2lin(d, 4), - b'\x00\x00\x08\x00\x00\x00\x08\x01\x00\x00\x10\x02') + encoded = b'\x00\x03\x24\x2a\x51\x54\x55\x58\x6b\x71\x7f'\ + b'\x80\x83\xa4\xaa\xd1\xd4\xd5\xd8\xeb\xf1\xff' + src = [-688, -720, -2240, -4032, -9, -3, -1, -27, -244, -82, -106, + 688, 720, 2240, 4032, 9, 3, 1, 27, 244, 82, 106] + for w in 1, 2, 4: + self.assertEqual(audioop.alaw2lin(encoded, w), + packs[w](*(x << (w * 8) >> 13 for x in src))) + + encoded = ''.join(chr(x) for x in xrange(256)) + for w in 2, 4: + decoded = audioop.alaw2lin(encoded, w) + self.assertEqual(audioop.lin2alaw(decoded, w), encoded) def test_lin2ulaw(self): - self.assertEqual(audioop.lin2ulaw(data[0], 1), '\xff\xe7\xdb') - self.assertEqual(audioop.lin2ulaw(data[1], 2), '\xff\xff\xff') - self.assertEqual(audioop.lin2ulaw(data[2], 4), '\xff\xff\xff') + self.assertEqual(audioop.lin2ulaw(datas[1], 1), + b'\xff\xad\x8e\x0e\x80\x00\x67') + self.assertEqual(audioop.lin2ulaw(datas[2], 2), + b'\xff\xad\x8e\x0e\x80\x00\x7e') + self.assertEqual(audioop.lin2ulaw(datas[4], 4), + b'\xff\xad\x8e\x0e\x80\x00\x7e') def test_ulaw2lin(self): - # Cursory - d = audioop.lin2ulaw(data[0], 1) - self.assertEqual(audioop.ulaw2lin(d, 1), data[0]) - if endian == 'big': - self.assertEqual(audioop.ulaw2lin(d, 2), - b'\x00\x00\x01\x04\x02\x0c') - self.assertEqual(audioop.ulaw2lin(d, 4), - b'\x00\x00\x00\x00\x01\x04\x00\x00\x02\x0c\x00\x00') - else: - self.assertEqual(audioop.ulaw2lin(d, 2), - b'\x00\x00\x04\x01\x0c\x02') - self.assertEqual(audioop.ulaw2lin(d, 4), - b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x0c\x02') + encoded = b'\x00\x0e\x28\x3f\x57\x6a\x76\x7c\x7e\x7f'\ + b'\x80\x8e\xa8\xbf\xd7\xea\xf6\xfc\xfe\xff' + src = [-8031, -4447, -1471, -495, -163, -53, -18, -6, -2, 0, + 8031, 4447, 1471, 495, 163, 53, 18, 6, 2, 0] + for w in 1, 2, 4: + self.assertEqual(audioop.ulaw2lin(encoded, w), + packs[w](*(x << (w * 8) >> 14 for x in src))) + + # Current u-law implementation has two codes fo 0: 0x7f and 0xff. + encoded = ''.join(chr(x) for x in range(127) + range(128, 256)) + for w in 2, 4: + decoded = audioop.ulaw2lin(encoded, w) + self.assertEqual(audioop.lin2ulaw(decoded, w), encoded) def test_mul(self): - data2 = [] - for d in data: - str = '' - for s in d: - str = str + chr(ord(s)*2) - data2.append(str) - self.assertEqual(audioop.mul(data[0], 1, 2), data2[0]) - self.assertEqual(audioop.mul(data[1],2, 2), data2[1]) - self.assertEqual(audioop.mul(data[2], 4, 2), data2[2]) + for w in 1, 2, 4: + self.assertEqual(audioop.mul(b'', w, 2), b'') + self.assertEqual(audioop.mul(datas[w], w, 0), + b'\0' * len(datas[w])) + self.assertEqual(audioop.mul(datas[w], w, 1), + datas[w]) + self.assertEqual(audioop.mul(datas[1], 1, 2), + b'\x00\x24\x7f\x80\x7f\x80\xfe') + self.assertEqual(audioop.mul(datas[2], 2, 2), + packs[2](0, 0x2468, 0x7fff, -0x8000, 0x7fff, -0x8000, -2)) + self.assertEqual(audioop.mul(datas[4], 4, 2), + packs[4](0, 0x2468acf0, 0x7fffffff, -0x80000000, + 0x7fffffff, -0x80000000, -2)) def test_ratecv(self): + for w in 1, 2, 4: + self.assertEqual(audioop.ratecv(b'', w, 1, 8000, 8000, None), + (b'', (-1, ((0, 0),)))) + self.assertEqual(audioop.ratecv(b'', w, 5, 8000, 8000, None), + (b'', (-1, ((0, 0),) * 5))) + self.assertEqual(audioop.ratecv(b'', w, 1, 8000, 16000, None), + (b'', (-2, ((0, 0),)))) + self.assertEqual(audioop.ratecv(datas[w], w, 1, 8000, 8000, None)[0], + datas[w]) state = None - d1, state = audioop.ratecv(data[0], 1, 1, 8000, 16000, state) - d2, state = audioop.ratecv(data[0], 1, 1, 8000, 16000, state) - self.assertEqual(d1 + d2, '\000\000\001\001\002\001\000\000\001\001\002') + d1, state = audioop.ratecv(b'\x00\x01\x02', 1, 1, 8000, 16000, state) + d2, state = audioop.ratecv(b'\x00\x01\x02', 1, 1, 8000, 16000, state) + self.assertEqual(d1 + d2, b'\000\000\001\001\002\001\000\000\001\001\002') + + for w in 1, 2, 4: + d0, state0 = audioop.ratecv(datas[w], w, 1, 8000, 16000, None) + d, state = b'', None + for i in range(0, len(datas[w]), w): + d1, state = audioop.ratecv(datas[w][i:i + w], w, 1, + 8000, 16000, state) + d += d1 + self.assertEqual(d, d0) + self.assertEqual(state, state0) def test_reverse(self): - self.assertEqual(audioop.reverse(data[0], 1), '\2\1\0') + for w in 1, 2, 4: + self.assertEqual(audioop.reverse(b'', w), b'') + self.assertEqual(audioop.reverse(packs[w](0, 1, 2), w), + packs[w](2, 1, 0)) def test_tomono(self): - data2 = '' - for d in data[0]: - data2 = data2 + d + d - self.assertEqual(audioop.tomono(data2, 1, 0.5, 0.5), data[0]) + for w in 1, 2, 4: + data1 = datas[w] + data2 = bytearray(2 * len(data1)) + for k in range(w): + data2[k::2*w] = data1[k::w] + self.assertEqual(audioop.tomono(str(data2), w, 1, 0), data1) + self.assertEqual(audioop.tomono(str(data2), w, 0, 1), b'\0' * len(data1)) + for k in range(w): + data2[k+w::2*w] = data1[k::w] + self.assertEqual(audioop.tomono(str(data2), w, 0.5, 0.5), data1) def test_tostereo(self): - data2 = '' - for d in data[0]: - data2 = data2 + d + d - self.assertEqual(audioop.tostereo(data[0], 1, 1, 1), data2) + for w in 1, 2, 4: + data1 = datas[w] + data2 = bytearray(2 * len(data1)) + for k in range(w): + data2[k::2*w] = data1[k::w] + self.assertEqual(audioop.tostereo(data1, w, 1, 0), data2) + self.assertEqual(audioop.tostereo(data1, w, 0, 0), b'\0' * len(data2)) + for k in range(w): + data2[k+w::2*w] = data1[k::w] + self.assertEqual(audioop.tostereo(data1, w, 1, 1), data2) def test_findfactor(self): - self.assertEqual(audioop.findfactor(data[1], data[1]), 1.0) + self.assertEqual(audioop.findfactor(datas[2], datas[2]), 1.0) + self.assertEqual(audioop.findfactor(b'\0' * len(datas[2]), datas[2]), + 0.0) def test_findfit(self): - self.assertEqual(audioop.findfit(data[1], data[1]), (0, 1.0)) + self.assertEqual(audioop.findfit(datas[2], datas[2]), (0, 1.0)) + self.assertEqual(audioop.findfit(datas[2], packs[2](1, 2, 0)), + (1, 8038.8)) + self.assertEqual(audioop.findfit(datas[2][:-2] * 5 + datas[2], datas[2]), + (30, 1.0)) def test_findmax(self): - self.assertEqual(audioop.findmax(data[1], 1), 2) + self.assertEqual(audioop.findmax(datas[2], 1), 5) def test_getsample(self): - for i in range(3): - self.assertEqual(audioop.getsample(data[0], 1, i), i) - self.assertEqual(audioop.getsample(data[1], 2, i), i) - self.assertEqual(audioop.getsample(data[2], 4, i), i) + for w in 1, 2, 4: + data = packs[w](0, 1, -1, maxvalues[w], minvalues[w]) + self.assertEqual(audioop.getsample(data, w, 0), 0) + self.assertEqual(audioop.getsample(data, w, 1), 1) + self.assertEqual(audioop.getsample(data, w, 2), -1) + self.assertEqual(audioop.getsample(data, w, 3), maxvalues[w]) + self.assertEqual(audioop.getsample(data, w, 4), minvalues[w]) def test_negativelen(self): # from issue 3306, previously it segfaulted @@ -220,9 +379,9 @@ self.assertRaises(audioop.error, audioop.lin2adpcm, data, size, state) def test_wrongsize(self): - data = b'abc' + data = b'abcdefgh' state = None - for size in (-1, 3, 5): + for size in (-1, 0, 3, 5, 1024): self.assertRaises(audioop.error, audioop.ulaw2lin, data, size) self.assertRaises(audioop.error, audioop.alaw2lin, data, size) self.assertRaises(audioop.error, audioop.adpcm2lin, data, size, state) diff --git a/lib-python/2.7/test/test_bigmem.py b/lib-python/2.7/test/test_bigmem.py --- a/lib-python/2.7/test/test_bigmem.py +++ b/lib-python/2.7/test/test_bigmem.py @@ -118,12 +118,13 @@ except MemoryError: pass # acceptable on 32-bit - @precisionbigmemtest(size=_2G-1, memuse=2) + @precisionbigmemtest(size=_2G-1, memuse=4) def test_decodeascii(self, size): return self.basic_encode_test(size, 'ascii', c='A') @precisionbigmemtest(size=_4G // 5, memuse=6+2) def test_unicode_repr_oflw(self, size): + self.skipTest("test crashes - see issue #14904") try: s = u"\uAAAA"*size r = repr(s) @@ -485,7 +486,7 @@ self.assertEqual(s.count('.'), 3) self.assertEqual(s.count('-'), size * 2) - @bigmemtest(minsize=_2G + 10, memuse=2) + @bigmemtest(minsize=_2G + 10, memuse=5) def test_repr_small(self, size): s = '-' * size s = repr(s) @@ -497,7 +498,6 @@ # repr() will create a string four times as large as this 'binary # string', but we don't want to allocate much more than twice # size in total. (We do extra testing in test_repr_large()) - size = size // 5 * 2 s = '\x00' * size s = repr(s) self.assertEqual(len(s), size * 4 + 2) @@ -541,7 +541,7 @@ self.assertEqual(len(s), size * 2) self.assertEqual(s.count('.'), size * 2) - @bigmemtest(minsize=_2G + 20, memuse=1) + @bigmemtest(minsize=_2G + 20, memuse=2) def test_slice_and_getitem(self, size): SUBSTR = '0123456789' sublen = len(SUBSTR) diff --git a/lib-python/2.7/test/test_bisect.py b/lib-python/2.7/test/test_bisect.py --- a/lib-python/2.7/test/test_bisect.py +++ b/lib-python/2.7/test/test_bisect.py @@ -23,6 +23,28 @@ import bisect as c_bisect +class Range(object): + """A trivial xrange()-like object without any integer width limitations.""" + def __init__(self, start, stop): + self.start = start + self.stop = stop + self.last_insert = None + + def __len__(self): + return self.stop - self.start + + def __getitem__(self, idx): + n = self.stop - self.start + if idx < 0: + idx += n + if idx >= n: + raise IndexError(idx) + return self.start + idx + + def insert(self, idx, item): + self.last_insert = idx, item + + class TestBisect(unittest.TestCase): module = None @@ -122,6 +144,35 @@ self.assertRaises(ValueError, mod.insort_left, [1, 2, 3], 5, -1, 3), self.assertRaises(ValueError, mod.insort_right, [1, 2, 3], 5, -1, 3), + def test_large_range(self): + # Issue 13496 + mod = self.module + n = sys.maxsize + try: + data = xrange(n-1) + except OverflowError: + self.skipTest("can't create a xrange() object of size `sys.maxsize`") + self.assertEqual(mod.bisect_left(data, n-3), n-3) + self.assertEqual(mod.bisect_right(data, n-3), n-2) + self.assertEqual(mod.bisect_left(data, n-3, n-10, n), n-3) + self.assertEqual(mod.bisect_right(data, n-3, n-10, n), n-2) + + def test_large_pyrange(self): + # Same as above, but without C-imposed limits on range() parameters + mod = self.module + n = sys.maxsize + data = Range(0, n-1) + self.assertEqual(mod.bisect_left(data, n-3), n-3) + self.assertEqual(mod.bisect_right(data, n-3), n-2) + self.assertEqual(mod.bisect_left(data, n-3, n-10, n), n-3) + self.assertEqual(mod.bisect_right(data, n-3, n-10, n), n-2) + x = n - 100 + mod.insort_left(data, x, x - 50, x + 50) + self.assertEqual(data.last_insert, (x, x)) + x = n - 200 + mod.insort_right(data, x, x - 50, x + 50) + self.assertEqual(data.last_insert, (x + 1, x)) + def test_random(self, n=25): from random import randrange for i in xrange(n): @@ -191,7 +242,7 @@ else: f = self.module.insort_right f(insorted, digit) - self.assertEqual(sorted(insorted), insorted) + self.assertEqual(sorted(insorted), insorted) def test_backcompatibility(self): self.assertEqual(self.module.insort, self.module.insort_right) diff --git a/lib-python/2.7/test/test_builtin.py b/lib-python/2.7/test/test_builtin.py --- a/lib-python/2.7/test/test_builtin.py +++ b/lib-python/2.7/test/test_builtin.py @@ -110,6 +110,7 @@ self.assertRaises(TypeError, all) # No args self.assertRaises(TypeError, all, [2, 4, 6], []) # Too many args self.assertEqual(all([]), True) # Empty iterator + self.assertEqual(all([0, TestFailingBool()]), False)# Short-circuit S = [50, 60] self.assertEqual(all(x > 42 for x in S), True) S = [50, 40, 60] @@ -119,11 +120,12 @@ self.assertEqual(any([None, None, None]), False) self.assertEqual(any([None, 4, None]), True) self.assertRaises(RuntimeError, any, [None, TestFailingBool(), 6]) - self.assertRaises(RuntimeError, all, TestFailingIter()) + self.assertRaises(RuntimeError, any, TestFailingIter()) self.assertRaises(TypeError, any, 10) # Non-iterable self.assertRaises(TypeError, any) # No args self.assertRaises(TypeError, any, [2, 4, 6], []) # Too many args self.assertEqual(any([]), False) # Empty iterator + self.assertEqual(any([1, TestFailingBool()]), True) # Short-circuit S = [40, 60, 30] self.assertEqual(any(x > 42 for x in S), True) S = [10, 20, 30] @@ -680,6 +682,8 @@ # Test input() later, together with raw_input + # test_int(): see test_int.py for int() tests. + def test_intern(self): self.assertRaises(TypeError, intern) # This fails if the test is run twice with a constant string, diff --git a/lib-python/2.7/test/test_bytes.py b/lib-python/2.7/test/test_bytes.py --- a/lib-python/2.7/test/test_bytes.py +++ b/lib-python/2.7/test/test_bytes.py @@ -635,6 +635,26 @@ b[3:0] = [42, 42, 42] self.assertEqual(b, bytearray([0, 1, 2, 42, 42, 42, 3, 4, 5, 6, 7, 8, 9])) + b[3:] = b'foo' + self.assertEqual(b, bytearray([0, 1, 2, 102, 111, 111])) + + b[:3] = memoryview(b'foo') + self.assertEqual(b, bytearray([102, 111, 111, 102, 111, 111])) + + b[3:4] = [] + self.assertEqual(b, bytearray([102, 111, 111, 111, 111])) + + b[1:] = list(b'uuuu') # this works only on Python2 + self.assertEqual(b, bytearray([102, 117, 117, 117, 117])) + + for elem in [5, -5, 0, long(10e20), u'str', 2.3, [u'a', u'b'], [[]]]: + with self.assertRaises(TypeError): + b[3:4] = elem + + for elem in [[254, 255, 256], [-256, 9000]]: + with self.assertRaises(ValueError): + b[3:4] = elem + def test_extended_set_del_slice(self): indices = (0, None, 1, 3, 19, 300, 1<<333, -1, -2, -31, -300) for start in indices: @@ -905,6 +925,7 @@ self.assertEqual(bytes(b"abc") < b"ab", False) self.assertEqual(bytes(b"abc") <= b"ab", False) + @test.test_support.requires_docstrings def test_doc(self): self.assertIsNotNone(bytearray.__doc__) self.assertTrue(bytearray.__doc__.startswith("bytearray("), bytearray.__doc__) diff --git a/lib-python/2.7/test/test_bz2.py b/lib-python/2.7/test/test_bz2.py --- a/lib-python/2.7/test/test_bz2.py +++ b/lib-python/2.7/test/test_bz2.py @@ -1,6 +1,6 @@ #!/usr/bin/env python from test import test_support -from test.test_support import TESTFN, import_module +from test.test_support import TESTFN, _4G, bigmemtest, import_module, findfile import unittest from cStringIO import StringIO @@ -23,6 +23,10 @@ TEXT = 'root:x:0:0:root:/root:/bin/bash\nbin:x:1:1:bin:/bin:\ndaemon:x:2:2:daemon:/sbin:\nadm:x:3:4:adm:/var/adm:\nlp:x:4:7:lp:/var/spool/lpd:\nsync:x:5:0:sync:/sbin:/bin/sync\nshutdown:x:6:0:shutdown:/sbin:/sbin/shutdown\nhalt:x:7:0:halt:/sbin:/sbin/halt\nmail:x:8:12:mail:/var/spool/mail:\nnews:x:9:13:news:/var/spool/news:\nuucp:x:10:14:uucp:/var/spool/uucp:\noperator:x:11:0:operator:/root:\ngames:x:12:100:games:/usr/games:\ngopher:x:13:30:gopher:/usr/lib/gopher-data:\nftp:x:14:50:FTP User:/var/ftp:/bin/bash\nnobody:x:65534:65534:Nobody:/home:\npostfix:x:100:101:postfix:/var/spool/postfix:\nniemeyer:x:500:500::/home/niemeyer:/bin/bash\npostgres:x:101:102:PostgreSQL Server:/var/lib/pgsql:/bin/bash\nmysql:x:102:103:MySQL server:/var/lib/mysql:/bin/bash\nwww:x:103:104::/var/www:/bin/false\n' DATA = 'BZh91AY&SY.\xc8N\x18\x00\x01>_\x80\x00\x10@\x02\xff\xf0\x01\x07n\x00?\xe7\xff\xe00\x01\x99\xaa\x00\xc0\x03F\x86\x8c#&\x83F\x9a\x03\x06\xa6\xd0\xa6\x93M\x0fQ\xa7\xa8\x06\x804hh\x12$\x11\xa4i4\xf14S\xd2\x88\xe5\xcd9gd6\x0b\n\xe9\x9b\xd5\x8a\x99\xf7\x08.K\x8ev\xfb\xf7xw\xbb\xdf\xa1\x92\xf1\xdd|/";\xa2\xba\x9f\xd5\xb1#A\xb6\xf6\xb3o\xc9\xc5y\\\xebO\xe7\x85\x9a\xbc\xb6f8\x952\xd5\xd7"%\x89>V,\xf7\xa6z\xe2\x9f\xa3\xdf\x11\x11"\xd6E)I\xa9\x13^\xca\xf3r\xd0\x03U\x922\xf26\xec\xb6\xed\x8b\xc3U\x13\x9d\xc5\x170\xa4\xfa^\x92\xacDF\x8a\x97\xd6\x19\xfe\xdd\xb8\xbd\x1a\x9a\x19\xa3\x80ankR\x8b\xe5\xd83]\xa9\xc6\x08\x82f\xf6\xb9"6l$\xb8j@\xc0\x8a\xb0l1..\xbak\x83ls\x15\xbc\xf4\xc1\x13\xbe\xf8E\xb8\x9d\r\xa8\x9dk\x84\xd3n\xfa\xacQ\x07\xb1%y\xaav\xb4\x08\xe0z\x1b\x16\xf5\x04\xe9\xcc\xb9\x08z\x1en7.G\xfc]\xc9\x14\xe1B@\xbb!8`' DATA_CRLF = 'BZh91AY&SY\xaez\xbbN\x00\x01H\xdf\x80\x00\x12@\x02\xff\xf0\x01\x07n\x00?\xe7\xff\xe0@\x01\xbc\xc6`\x86*\x8d=M\xa9\x9a\x86\xd0L@\x0fI\xa6!\xa1\x13\xc8\x88jdi\x8d@\x03@\x1a\x1a\x0c\x0c\x83 \x00\xc4h2\x19\x01\x82D\x84e\t\xe8\x99\x89\x19\x1ah\x00\r\x1a\x11\xaf\x9b\x0fG\xf5(\x1b\x1f?\t\x12\xcf\xb5\xfc\x95E\x00ps\x89\x12^\xa4\xdd\xa2&\x05(\x87\x04\x98\x89u\xe40%\xb6\x19\'\x8c\xc4\x89\xca\x07\x0e\x1b!\x91UIFU%C\x994!DI\xd2\xfa\xf0\xf1N8W\xde\x13A\xf5\x9cr%?\x9f3;I45A\xd1\x8bT\xb1\xa4\xc7\x8d\x1a\\"\xad\xa1\xabyBg\x15\xb9l\x88\x88\x91k"\x94\xa4\xd4\x89\xae*\xa6\x0b\x10\x0c\xd6\xd4m\xe86\xec\xb5j\x8a\x86j\';\xca.\x01I\xf2\xaaJ\xe8\x88\x8cU+t3\xfb\x0c\n\xa33\x13r2\r\x16\xe0\xb3(\xbf\x1d\x83r\xe7M\xf0D\x1365\xd8\x88\xd3\xa4\x92\xcb2\x06\x04\\\xc1\xb0\xea//\xbek&\xd8\xe6+t\xe5\xa1\x13\xada\x16\xder5"w]\xa2i\xb7[\x97R \xe2IT\xcd;Z\x04dk4\xad\x8a\t\xd3\x81z\x10\xf1:^`\xab\x1f\xc5\xdc\x91N\x14$+\x9e\xae\xd3\x80' + EMPTY_DATA = 'BZh9\x17rE8P\x90\x00\x00\x00\x00' + + with open(findfile("testbz2_bigmem.bz2"), "rb") as f: + DATA_BIGMEM = f.read() if has_cmdline_bunzip2: def decompress(self, data): @@ -43,6 +47,7 @@ def decompress(self, data): return bz2.decompress(data) + class BZ2FileTest(BaseTest): "Test BZ2File type miscellaneous methods." @@ -323,6 +328,24 @@ self.assertRaises(ValueError, f.readline) self.assertRaises(ValueError, f.readlines) + def test_read_truncated(self): + # Drop the eos_magic field (6 bytes) and CRC (4 bytes). + truncated = self.DATA[:-10] + with open(self.filename, 'wb') as f: + f.write(truncated) + with BZ2File(self.filename) as f: + self.assertRaises(EOFError, f.read) + with BZ2File(self.filename) as f: + self.assertEqual(f.read(len(self.TEXT)), self.TEXT) + self.assertRaises(EOFError, f.read, 1) + # Incomplete 4-byte file header, and block header of at least 146 bits. + for i in range(22): + with open(self.filename, 'wb') as f: + f.write(truncated[:i]) + with BZ2File(self.filename) as f: + self.assertRaises(EOFError, f.read, 1) + + class BZ2CompressorTest(BaseTest): def testCompress(self): # "Test BZ2Compressor.compress()/flush()" @@ -332,6 +355,13 @@ data += bz2c.flush() self.assertEqual(self.decompress(data), self.TEXT) + def testCompressEmptyString(self): + # "Test BZ2Compressor.compress()/flush() of empty string" + bz2c = BZ2Compressor() + data = bz2c.compress('') + data += bz2c.flush() + self.assertEqual(data, self.EMPTY_DATA) + def testCompressChunks10(self): # "Test BZ2Compressor.compress()/flush() with chunks of 10 bytes" bz2c = BZ2Compressor() @@ -346,6 +376,17 @@ data += bz2c.flush() self.assertEqual(self.decompress(data), self.TEXT) + @bigmemtest(_4G, memuse=1.25) + def testBigmem(self, size): + text = "a" * size + bz2c = bz2.BZ2Compressor() + data = bz2c.compress(text) + bz2c.flush() + del text + text = self.decompress(data) + self.assertEqual(len(text), size) + self.assertEqual(text.strip("a"), "") + + class BZ2DecompressorTest(BaseTest): def test_Constructor(self): self.assertRaises(TypeError, BZ2Decompressor, 42) @@ -383,6 +424,16 @@ bz2d = BZ2Decompressor() text = bz2d.decompress(self.DATA) self.assertRaises(EOFError, bz2d.decompress, "anything") + self.assertRaises(EOFError, bz2d.decompress, "") + + @bigmemtest(_4G, memuse=1.25) + def testBigmem(self, size): + # Issue #14398: decompression fails when output data is >=2GB. + if size < _4G: + self.skipTest("Test needs 5GB of memory to run.") + text = bz2.BZ2Decompressor().decompress(self.DATA_BIGMEM) + self.assertEqual(len(text), _4G) + self.assertEqual(text.strip("\0"), "") class FuncTest(BaseTest): @@ -393,6 +444,11 @@ data = bz2.compress(self.TEXT) self.assertEqual(self.decompress(data), self.TEXT) + def testCompressEmptyString(self): + # "Test compress() of empty string" + text = bz2.compress('') + self.assertEqual(text, self.EMPTY_DATA) + def testDecompress(self): # "Test decompress() function" text = bz2.decompress(self.DATA) @@ -403,10 +459,33 @@ text = bz2.decompress("") self.assertEqual(text, "") + def testDecompressToEmptyString(self): + # "Test decompress() of minimal bz2 data to empty string" + text = bz2.decompress(self.EMPTY_DATA) + self.assertEqual(text, '') + def testDecompressIncomplete(self): # "Test decompress() function with incomplete data" self.assertRaises(ValueError, bz2.decompress, self.DATA[:-10]) + @bigmemtest(_4G, memuse=1.25) + def testCompressBigmem(self, size): + text = "a" * size + data = bz2.compress(text) + del text + text = self.decompress(data) + self.assertEqual(len(text), size) + self.assertEqual(text.strip("a"), "") + + @bigmemtest(_4G, memuse=1.25) + def testDecompressBigmem(self, size): + # Issue #14398: decompression fails when output data is >=2GB. + if size < _4G: + self.skipTest("Test needs 5GB of memory to run.") + text = bz2.decompress(self.DATA_BIGMEM) + self.assertEqual(len(text), _4G) + self.assertEqual(text.strip("\0"), "") + def test_main(): test_support.run_unittest( BZ2FileTest, diff --git a/lib-python/2.7/test/test_calendar.py b/lib-python/2.7/test/test_calendar.py --- a/lib-python/2.7/test/test_calendar.py +++ b/lib-python/2.7/test/test_calendar.py @@ -3,6 +3,7 @@ from test import test_support import locale +import datetime result_2004_text = """ @@ -254,13 +255,30 @@ # (it is still not thread-safe though) old_october = calendar.TextCalendar().formatmonthname(2010, 10, 10) try: - calendar.LocaleTextCalendar(locale='').formatmonthname(2010, 10, 10) + cal = calendar.LocaleTextCalendar(locale='') + local_weekday = cal.formatweekday(1, 10) + local_month = cal.formatmonthname(2010, 10, 10) except locale.Error: # cannot set the system default locale -- skip rest of test - return - calendar.LocaleHTMLCalendar(locale='').formatmonthname(2010, 10) + raise unittest.SkipTest('cannot set the system default locale') + # should be encodable + local_weekday.encode('utf-8') + local_month.encode('utf-8') + self.assertEqual(len(local_weekday), 10) + self.assertGreaterEqual(len(local_month), 10) + cal = calendar.LocaleHTMLCalendar(locale='') + local_weekday = cal.formatweekday(1) + local_month = cal.formatmonthname(2010, 10) + # should be encodable + local_weekday.encode('utf-8') + local_month.encode('utf-8') new_october = calendar.TextCalendar().formatmonthname(2010, 10, 10) - self.assertEquals(old_october, new_october) + self.assertEqual(old_october, new_october) + + def test_itermonthdates(self): + # ensure itermonthdates doesn't overflow after datetime.MAXYEAR + # see #15421 + list(calendar.Calendar().itermonthdates(datetime.MAXYEAR, 12)) class MonthCalendarTestCase(unittest.TestCase): diff --git a/lib-python/2.7/test/test_capi.py b/lib-python/2.7/test/test_capi.py --- a/lib-python/2.7/test/test_capi.py +++ b/lib-python/2.7/test/test_capi.py @@ -8,8 +8,10 @@ import unittest from test import test_support try: + import thread import threading except ImportError: + thread = None threading = None import _testcapi @@ -96,8 +98,32 @@ self.pendingcalls_wait(l, n) + at unittest.skipUnless(threading and thread, 'Threading required for this test.') +class TestThreadState(unittest.TestCase): + + @test_support.reap_threads + def test_thread_state(self): + # some extra thread-state tests driven via _testcapi + def target(): + idents = [] + + def callback(): + idents.append(thread.get_ident()) + + _testcapi._test_thread_state(callback) + a = b = callback + time.sleep(1) + # Check our main thread is in the list exactly 3 times. + self.assertEqual(idents.count(thread.get_ident()), 3, + "Couldn't find main thread correctly in the list") + + target() + t = threading.Thread(target=target) + t.start() + t.join() + + def test_main(): - for name in dir(_testcapi): if name.startswith('test_'): test = getattr(_testcapi, name) @@ -108,33 +134,7 @@ except _testcapi.error: raise test_support.TestFailed, sys.exc_info()[1] - # some extra thread-state tests driven via _testcapi - def TestThreadState(): - if test_support.verbose: - print "auto-thread-state" - - idents = [] - - def callback(): - idents.append(thread.get_ident()) - - _testcapi._test_thread_state(callback) - a = b = callback - time.sleep(1) - # Check our main thread is in the list exactly 3 times. - if idents.count(thread.get_ident()) != 3: - raise test_support.TestFailed, \ - "Couldn't find main thread correctly in the list" - - if threading: - import thread - import time - TestThreadState() - t=threading.Thread(target=TestThreadState) - t.start() - t.join() - - test_support.run_unittest(TestPendingCalls) + test_support.run_unittest(TestPendingCalls, TestThreadState) if __name__ == "__main__": test_main() diff --git a/lib-python/2.7/test/test_cmd.py b/lib-python/2.7/test/test_cmd.py --- a/lib-python/2.7/test/test_cmd.py +++ b/lib-python/2.7/test/test_cmd.py @@ -84,11 +84,11 @@ Documented commands (type help ): ======================================== - add + add help Undocumented commands: ====================== - exit help shell + exit shell Test for the function print_topics(): @@ -125,11 +125,11 @@ Documented commands (type help ): ======================================== - add + add help Undocumented commands: ====================== - exit help shell + exit shell help text for add Hello from postloop diff --git a/lib-python/2.7/test/test_cmd_line.py b/lib-python/2.7/test/test_cmd_line.py --- a/lib-python/2.7/test/test_cmd_line.py +++ b/lib-python/2.7/test/test_cmd_line.py @@ -2,9 +2,13 @@ # All tests are executed with environment variables ignored # See test_cmd_line_script.py for testing of script execution -import test.test_support, unittest +import test.test_support import sys -from test.script_helper import spawn_python, kill_python, python_exit_code +import unittest +from test.script_helper import ( + assert_python_ok, assert_python_failure, spawn_python, kill_python, + python_exit_code +) class CmdLineTest(unittest.TestCase): @@ -101,6 +105,36 @@ data = self.start_python('-R', '-c', code) self.assertTrue('hash_randomization=1' in data) + def test_del___main__(self): + # Issue #15001: PyRun_SimpleFileExFlags() did crash because it kept a + # borrowed reference to the dict of __main__ module and later modify + # the dict whereas the module was destroyed + filename = test.test_support.TESTFN + self.addCleanup(test.test_support.unlink, filename) + with open(filename, "w") as script: + print >>script, "import sys" + print >>script, "del sys.modules['__main__']" + assert_python_ok(filename) + + def test_unknown_options(self): + rc, out, err = assert_python_failure('-E', '-z') + self.assertIn(b'Unknown option: -z', err) + self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) + self.assertEqual(b'', out) + # Add "without='-E'" to prevent _assert_python to append -E + # to env_vars and change the output of stderr + rc, out, err = assert_python_failure('-z', without='-E') + self.assertIn(b'Unknown option: -z', err) + self.assertEqual(err.splitlines().count(b'Unknown option: -z'), 1) + self.assertEqual(b'', out) + rc, out, err = assert_python_failure('-a', '-z', without='-E') + self.assertIn(b'Unknown option: -a', err) + # only the first unknown option is reported + self.assertNotIn(b'Unknown option: -z', err) + self.assertEqual(err.splitlines().count(b'Unknown option: -a'), 1) + self.assertEqual(b'', out) + + def test_main(): test.test_support.run_unittest(CmdLineTest) test.test_support.reap_children() diff --git a/lib-python/2.7/test/test_cmd_line_script.py b/lib-python/2.7/test/test_cmd_line_script.py --- a/lib-python/2.7/test/test_cmd_line_script.py +++ b/lib-python/2.7/test/test_cmd_line_script.py @@ -6,11 +6,14 @@ import test.test_support from test.script_helper import (run_python, temp_dir, make_script, compile_script, - make_pkg, make_zip_script, make_zip_pkg) + assert_python_failure, make_pkg, + make_zip_script, make_zip_pkg) verbose = test.test_support.verbose +example_args = ['test1', 'test2', 'test3'] + test_source = """\ # Script may be run with optimisation enabled, so don't rely on assert # statements being executed @@ -204,6 +207,19 @@ launch_name = _make_launch_script(script_dir, 'launch', 'test_pkg') self._check_import_error(launch_name, msg) + def test_dash_m_error_code_is_one(self): + # If a module is invoked with the -m command line flag + # and results in an error that the return code to the + # shell is '1' + with temp_dir() as script_dir: + pkg_dir = os.path.join(script_dir, 'test_pkg') + make_pkg(pkg_dir) + script_name = _make_test_script(pkg_dir, 'other', "if __name__ == '__main__': raise ValueError") + rc, out, err = assert_python_failure('-m', 'test_pkg.other', *example_args) + if verbose > 1: + print(out) + self.assertEqual(rc, 1) + def test_main(): test.test_support.run_unittest(CmdLineTest) diff --git a/lib-python/2.7/test/test_codeccallbacks.py b/lib-python/2.7/test/test_codeccallbacks.py --- a/lib-python/2.7/test/test_codeccallbacks.py +++ b/lib-python/2.7/test/test_codeccallbacks.py @@ -262,12 +262,12 @@ self.assertEqual( "\\u3042\u3xxx".decode("unicode-escape", "test.handler1"), - u"\u3042[<92><117><51><120>]xx" + u"\u3042[<92><117><51>]xxx" ) self.assertEqual( "\\u3042\u3xx".decode("unicode-escape", "test.handler1"), - u"\u3042[<92><117><51><120><120>]" + u"\u3042[<92><117><51>]xx" ) self.assertEqual( @@ -717,7 +717,7 @@ raise ValueError self.assertRaises(UnicodeError, codecs.charmap_decode, "\xff", "strict", {0xff: None}) self.assertRaises(ValueError, codecs.charmap_decode, "\xff", "strict", D()) - self.assertRaises(TypeError, codecs.charmap_decode, "\xff", "strict", {0xff: sys.maxunicode+1}) + self.assertRaises(TypeError, codecs.charmap_decode, "\xff", "strict", {0xff: 0x110000}) def test_encodehelper(self): # enhance coverage of: diff --git a/lib-python/2.7/test/test_codecs.py b/lib-python/2.7/test/test_codecs.py --- a/lib-python/2.7/test/test_codecs.py +++ b/lib-python/2.7/test/test_codecs.py @@ -4,6 +4,11 @@ import locale import sys, StringIO, _testcapi +def coding_checker(self, coder): + def check(input, expect): + self.assertEqual(coder(input), (expect, len(input))) + return check + class Queue(object): """ queue: write bytes at one end, read bytes from the other end @@ -281,7 +286,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", # first byte of BOM read u"", # second byte of BOM read @@ -303,6 +308,10 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) @@ -331,7 +340,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", u"", @@ -349,6 +358,10 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) @@ -371,7 +384,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", u"", @@ -389,6 +402,10 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) @@ -439,7 +456,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", # first byte of BOM read u"", # second byte of BOM read => byteorder known @@ -451,6 +468,10 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) @@ -481,7 +502,7 @@ def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", u"\x00", @@ -491,18 +512,34 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) def test_errors(self): - self.assertRaises(UnicodeDecodeError, codecs.utf_16_le_decode, "\xff", "strict", True) + tests = [ + (b'\xff', u'\ufffd'), + (b'A\x00Z', u'A\ufffd'), + (b'A\x00B\x00C\x00D\x00Z', u'ABCD\ufffd'), + (b'\x00\xd8', u'\ufffd'), + (b'\x00\xd8A', u'\ufffd'), + (b'\x00\xd8A\x00', u'\ufffdA'), + (b'\x00\xdcA\x00', u'\ufffdA'), + ] + for raw, expected in tests: + self.assertRaises(UnicodeDecodeError, codecs.utf_16_le_decode, + raw, 'strict', True) + self.assertEqual(raw.decode('utf-16le', 'replace'), expected) class UTF16BETest(ReadTest): encoding = "utf-16-be" def test_partial(self): self.check_partial( - u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", [ u"", u"\x00", @@ -512,18 +549,34 @@ u"\x00\xff\u0100", u"\x00\xff\u0100", u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff", + u"\x00\xff\u0100\uffff\U00010000", ] ) def test_errors(self): - self.assertRaises(UnicodeDecodeError, codecs.utf_16_be_decode, "\xff", "strict", True) + tests = [ + (b'\xff', u'\ufffd'), + (b'\x00A\xff', u'A\ufffd'), + (b'\x00A\x00B\x00C\x00DZ', u'ABCD\ufffd'), + (b'\xd8\x00', u'\ufffd'), + (b'\xd8\x00\xdc', u'\ufffd'), + (b'\xd8\x00\x00A', u'\ufffdA'), + (b'\xdc\x00\x00A', u'\ufffdA'), + ] + for raw, expected in tests: + self.assertRaises(UnicodeDecodeError, codecs.utf_16_be_decode, + raw, 'strict', True) + self.assertEqual(raw.decode('utf-16be', 'replace'), expected) class UTF8Test(ReadTest): encoding = "utf-8" def test_partial(self): self.check_partial( - u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff\U00010000", [ u"\x00", u"\x00", @@ -536,6 +589,10 @@ u"\x00\xff\u07ff\u0800", u"\x00\xff\u07ff\u0800", u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff", + u"\x00\xff\u07ff\u0800\uffff\U00010000", ] ) @@ -595,7 +652,7 @@ def test_partial(self): self.check_partial( - u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff\U00010000", [ u"", u"", @@ -614,6 +671,10 @@ u"\ufeff\x00\xff\u07ff\u0800", u"\ufeff\x00\xff\u07ff\u0800", u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff", + u"\ufeff\x00\xff\u07ff\u0800\uffff\U00010000", ] ) @@ -674,6 +735,54 @@ def test_empty(self): self.assertEqual(codecs.escape_decode(""), ("", 0)) + def test_raw(self): + decode = codecs.escape_decode + for b in range(256): + b = chr(b) + if b != '\\': + self.assertEqual(decode(b + '0'), (b + '0', 2)) + + def test_escape(self): + decode = codecs.escape_decode + check = coding_checker(self, decode) + check(b"[\\\n]", b"[]") + check(br'[\"]', b'["]') + check(br"[\']", b"[']") + check(br"[\\]", br"[\]") + check(br"[\a]", b"[\x07]") + check(br"[\b]", b"[\x08]") + check(br"[\t]", b"[\x09]") + check(br"[\n]", b"[\x0a]") + check(br"[\v]", b"[\x0b]") + check(br"[\f]", b"[\x0c]") + check(br"[\r]", b"[\x0d]") + check(br"[\7]", b"[\x07]") + check(br"[\8]", br"[\8]") + check(br"[\78]", b"[\x078]") + check(br"[\41]", b"[!]") + check(br"[\418]", b"[!8]") + check(br"[\101]", b"[A]") + check(br"[\1010]", b"[A0]") + check(br"[\501]", b"[A]") + check(br"[\x41]", b"[A]") + check(br"[\X41]", br"[\X41]") + check(br"[\x410]", b"[A0]") + for b in range(256): + b = chr(b) + if b not in '\n"\'\\abtnvfr01234567x': + check('\\' + b, '\\' + b) + + def test_errors(self): + decode = codecs.escape_decode + self.assertRaises(ValueError, decode, br"\x") + self.assertRaises(ValueError, decode, br"[\x]") + self.assertEqual(decode(br"[\x]\x", "ignore"), (b"[]", 6)) + self.assertEqual(decode(br"[\x]\x", "replace"), (b"[?]?", 6)) + self.assertRaises(ValueError, decode, br"\x0") + self.assertRaises(ValueError, decode, br"[\x0]") + self.assertEqual(decode(br"[\x0]\x0", "ignore"), (b"[]", 8)) + self.assertEqual(decode(br"[\x0]\x0", "replace"), (b"[?]?", 8)) + class RecodingTest(unittest.TestCase): def test_recoding(self): f = StringIO.StringIO() @@ -1495,6 +1604,14 @@ (u"abc", 3) ) + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, b"\x00\x01\x02", "strict", u"ab" + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", u"ab\ufffe" + ) + self.assertEqual( codecs.charmap_decode("\x00\x01\x02", "replace", u"ab"), (u"ab\ufffd", 3) @@ -1521,6 +1638,149 @@ (u"", len(allbytes)) ) + def test_decode_with_int2str_map(self): + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: u'a', 1: u'b', 2: u'c'}), + (u"abc", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: u'Aa', 1: u'Bb', 2: u'Cc'}), + (u"AaBbCc", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: u'\U0010FFFF', 1: u'b', 2: u'c'}), + (u"\U0010FFFFbc", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: u'a', 1: u'b', 2: u''}), + (u"ab", 3) + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: u'a', 1: u'b'} + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: u'a', 1: u'b', 2: None} + ) + + # Issue #14850 + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: u'a', 1: u'b', 2: u'\ufffe'} + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: u'a', 1: u'b'}), + (u"ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: u'a', 1: u'b', 2: None}), + (u"ab\ufffd", 3) + ) + + # Issue #14850 + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: u'a', 1: u'b', 2: u'\ufffe'}), + (u"ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: u'a', 1: u'b'}), + (u"ab", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: u'a', 1: u'b', 2: None}), + (u"ab", 3) + ) + + # Issue #14850 + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: u'a', 1: u'b', 2: u'\ufffe'}), + (u"ab", 3) + ) + + allbytes = "".join(chr(i) for i in xrange(256)) + self.assertEqual( + codecs.charmap_decode(allbytes, "ignore", {}), + (u"", len(allbytes)) + ) + + def test_decode_with_int2int_map(self): + a = ord(u'a') + b = ord(u'b') + c = ord(u'c') + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: a, 1: b, 2: c}), + (u"abc", 3) + ) + + # Issue #15379 + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "strict", + {0: 0x10FFFF, 1: b, 2: c}), + (u"\U0010FFFFbc", 3) + ) + + self.assertRaises(TypeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: 0x110000, 1: b, 2: c} + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: a, 1: b}, + ) + + self.assertRaises(UnicodeDecodeError, + codecs.charmap_decode, "\x00\x01\x02", "strict", + {0: a, 1: b, 2: 0xFFFE}, + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: a, 1: b}), + (u"ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "replace", + {0: a, 1: b, 2: 0xFFFE}), + (u"ab\ufffd", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: a, 1: b}), + (u"ab", 3) + ) + + self.assertEqual( + codecs.charmap_decode("\x00\x01\x02", "ignore", + {0: a, 1: b, 2: 0xFFFE}), + (u"ab", 3) + ) + + class WithStmtTest(unittest.TestCase): def test_encodedfile(self): f = StringIO.StringIO("\xc3\xbc") @@ -1535,6 +1795,134 @@ self.assertEqual(srw.read(), u"\xfc") +class UnicodeEscapeTest(unittest.TestCase): + def test_empty(self): + self.assertEqual(codecs.unicode_escape_encode(u""), ("", 0)) + self.assertEqual(codecs.unicode_escape_decode(""), (u"", 0)) + + def test_raw_encode(self): + encode = codecs.unicode_escape_encode + for b in range(32, 127): + if b != ord('\\'): + self.assertEqual(encode(unichr(b)), (chr(b), 1)) + + def test_raw_decode(self): + decode = codecs.unicode_escape_decode + for b in range(256): + if b != ord('\\'): + self.assertEqual(decode(chr(b) + '0'), (unichr(b) + u'0', 2)) + + def test_escape_encode(self): + encode = codecs.unicode_escape_encode + check = coding_checker(self, encode) + check(u'\t', r'\t') + check(u'\n', r'\n') + check(u'\r', r'\r') + check(u'\\', r'\\') + for b in range(32): + if chr(b) not in '\t\n\r': + check(unichr(b), '\\x%02x' % b) + for b in range(127, 256): + check(unichr(b), '\\x%02x' % b) + check(u'\u20ac', r'\u20ac') + check(u'\U0001d120', r'\U0001d120') + + def test_escape_decode(self): + decode = codecs.unicode_escape_decode + check = coding_checker(self, decode) + check("[\\\n]", u"[]") + check(r'[\"]', u'["]') + check(r"[\']", u"[']") + check(r"[\\]", ur"[\]") + check(r"[\a]", u"[\x07]") + check(r"[\b]", u"[\x08]") + check(r"[\t]", u"[\x09]") + check(r"[\n]", u"[\x0a]") + check(r"[\v]", u"[\x0b]") + check(r"[\f]", u"[\x0c]") + check(r"[\r]", u"[\x0d]") + check(r"[\7]", u"[\x07]") + check(r"[\8]", ur"[\8]") + check(r"[\78]", u"[\x078]") + check(r"[\41]", u"[!]") + check(r"[\418]", u"[!8]") + check(r"[\101]", u"[A]") + check(r"[\1010]", u"[A0]") + check(r"[\x41]", u"[A]") + check(r"[\x410]", u"[A0]") + check(r"\u20ac", u"\u20ac") + check(r"\U0001d120", u"\U0001d120") + for b in range(256): + if chr(b) not in '\n"\'\\abtnvfr01234567xuUN': + check('\\' + chr(b), u'\\' + unichr(b)) + + def test_decode_errors(self): + decode = codecs.unicode_escape_decode + for c, d in ('x', 2), ('u', 4), ('U', 4): + for i in range(d): + self.assertRaises(UnicodeDecodeError, decode, + "\\" + c + "0"*i) + self.assertRaises(UnicodeDecodeError, decode, + "[\\" + c + "0"*i + "]") + data = "[\\" + c + "0"*i + "]\\" + c + "0"*i + self.assertEqual(decode(data, "ignore"), (u"[]", len(data))) + self.assertEqual(decode(data, "replace"), + (u"[\ufffd]\ufffd", len(data))) + self.assertRaises(UnicodeDecodeError, decode, r"\U00110000") + self.assertEqual(decode(r"\U00110000", "ignore"), (u"", 10)) + self.assertEqual(decode(r"\U00110000", "replace"), (u"\ufffd", 10)) + + +class RawUnicodeEscapeTest(unittest.TestCase): + def test_empty(self): + self.assertEqual(codecs.raw_unicode_escape_encode(u""), ("", 0)) + self.assertEqual(codecs.raw_unicode_escape_decode(""), (u"", 0)) + + def test_raw_encode(self): + encode = codecs.raw_unicode_escape_encode + for b in range(256): + self.assertEqual(encode(unichr(b)), (chr(b), 1)) + + def test_raw_decode(self): + decode = codecs.raw_unicode_escape_decode + for b in range(256): + self.assertEqual(decode(chr(b) + '0'), (unichr(b) + u'0', 2)) + + def test_escape_encode(self): + encode = codecs.raw_unicode_escape_encode + check = coding_checker(self, encode) + for b in range(256): + if chr(b) not in 'uU': + check(u'\\' + unichr(b), '\\' + chr(b)) + check(u'\u20ac', r'\u20ac') + check(u'\U0001d120', r'\U0001d120') + + def test_escape_decode(self): + decode = codecs.raw_unicode_escape_decode + check = coding_checker(self, decode) + for b in range(256): + if chr(b) not in 'uU': + check('\\' + chr(b), u'\\' + unichr(b)) + check(r"\u20ac", u"\u20ac") + check(r"\U0001d120", u"\U0001d120") + + def test_decode_errors(self): + decode = codecs.raw_unicode_escape_decode + for c, d in ('u', 4), ('U', 4): + for i in range(d): + self.assertRaises(UnicodeDecodeError, decode, + "\\" + c + "0"*i) + self.assertRaises(UnicodeDecodeError, decode, + "[\\" + c + "0"*i + "]") + data = "[\\" + c + "0"*i + "]\\" + c + "0"*i + self.assertEqual(decode(data, "ignore"), (u"[]", len(data))) + self.assertEqual(decode(data, "replace"), + (u"[\ufffd]\ufffd", len(data))) + self.assertRaises(UnicodeDecodeError, decode, r"\U00110000") + self.assertEqual(decode(r"\U00110000", "ignore"), (u"", 10)) + self.assertEqual(decode(r"\U00110000", "replace"), (u"\ufffd", 10)) + + class BomTest(unittest.TestCase): def test_seek0(self): data = u"1234567890" @@ -1620,6 +2008,8 @@ BasicStrTest, CharmapTest, WithStmtTest, + UnicodeEscapeTest, + RawUnicodeEscapeTest, BomTest, ) diff --git a/lib-python/2.7/test/test_codeop.py b/lib-python/2.7/test/test_codeop.py --- a/lib-python/2.7/test/test_codeop.py +++ b/lib-python/2.7/test/test_codeop.py @@ -50,7 +50,7 @@ '''succeed iff str is the start of an invalid piece of code''' try: compile_command(str,symbol=symbol) - self.fail("No exception thrown for invalid code") + self.fail("No exception raised for invalid code") except SyntaxError: self.assertTrue(is_syntax) except OverflowError: diff --git a/lib-python/2.7/test/test_compile.py b/lib-python/2.7/test/test_compile.py --- a/lib-python/2.7/test/test_compile.py +++ b/lib-python/2.7/test/test_compile.py @@ -61,6 +61,34 @@ except SyntaxError: pass + def test_exec_functional_style(self): + # Exec'ing a tuple of length 2 works. + g = {'b': 2} + exec("a = b + 1", g) + self.assertEqual(g['a'], 3) + + # As does exec'ing a tuple of length 3. + l = {'b': 3} + g = {'b': 5, 'c': 7} + exec("a = b + c", g, l) + self.assertNotIn('a', g) + self.assertEqual(l['a'], 10) + + # Tuples not of length 2 or 3 are invalid. + with self.assertRaises(TypeError): + exec("a = b + 1",) + + with self.assertRaises(TypeError): + exec("a = b + 1", {}, {}, {}) + + # Can't mix and match the two calling forms. + g = {'a': 3, 'b': 4} + l = {} + with self.assertRaises(TypeError): + exec("a = b + 1", g) in g + with self.assertRaises(TypeError): + exec("a = b + 1", g, l) in g, l + def test_exec_with_general_mapping_for_locals(self): class M: diff --git a/lib-python/2.7/test/test_cookie.py b/lib-python/2.7/test/test_cookie.py --- a/lib-python/2.7/test/test_cookie.py +++ b/lib-python/2.7/test/test_cookie.py @@ -64,13 +64,13 @@ # loading 'expires' C = Cookie.SimpleCookie() - C.load('Customer="W"; expires=Wed, 01-Jan-2010 00:00:00 GMT') + C.load('Customer="W"; expires=Wed, 01 Jan 2010 00:00:00 GMT') self.assertEqual(C['Customer']['expires'], - 'Wed, 01-Jan-2010 00:00:00 GMT') + 'Wed, 01 Jan 2010 00:00:00 GMT') C = Cookie.SimpleCookie() - C.load('Customer="W"; expires=Wed, 01-Jan-98 00:00:00 GMT') + C.load('Customer="W"; expires=Wed, 01 Jan 98 00:00:00 GMT') self.assertEqual(C['Customer']['expires'], - 'Wed, 01-Jan-98 00:00:00 GMT') + 'Wed, 01 Jan 98 00:00:00 GMT') def test_extended_encode(self): # Issue 9824: some browsers don't follow the standard; we now @@ -90,9 +90,10 @@ def test_main(): run_unittest(CookieTests) - with check_warnings(('.+Cookie class is insecure; do not use it', - DeprecationWarning)): - run_doctest(Cookie) + if Cookie.__doc__ is not None: + with check_warnings(('.+Cookie class is insecure; do not use it', + DeprecationWarning)): + run_doctest(Cookie) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_cpickle.py b/lib-python/2.7/test/test_cpickle.py --- a/lib-python/2.7/test/test_cpickle.py +++ b/lib-python/2.7/test/test_cpickle.py @@ -1,7 +1,9 @@ import cPickle, unittest from cStringIO import StringIO -from test.pickletester import AbstractPickleTests, AbstractPickleModuleTests -from test.pickletester import AbstractPicklerUnpicklerObjectTests +from test.pickletester import (AbstractPickleTests, + AbstractPickleModuleTests, + AbstractPicklerUnpicklerObjectTests, + BigmemPickleTests) from test import test_support class cPickleTests(AbstractPickleTests, AbstractPickleModuleTests): @@ -101,6 +103,16 @@ pickler_class = cPickle.Pickler unpickler_class = cPickle.Unpickler +class cPickleBigmemPickleTests(BigmemPickleTests): + + def dumps(self, arg, proto=0, fast=0): + # Ignore fast + return cPickle.dumps(arg, proto) + + def loads(self, buf): + # Ignore fast + return cPickle.loads(buf) + class Node(object): pass @@ -133,6 +145,7 @@ cPickleFastPicklerTests, cPickleDeepRecursive, cPicklePicklerUnpicklerObjectTests, + cPickleBigmemPickleTests, ) if __name__ == "__main__": diff --git a/lib-python/2.7/test/test_csv.py b/lib-python/2.7/test/test_csv.py --- a/lib-python/2.7/test/test_csv.py +++ b/lib-python/2.7/test/test_csv.py @@ -243,6 +243,15 @@ self.assertRaises(csv.Error, self._read_test, ['a,b\nc,d'], []) self.assertRaises(csv.Error, self._read_test, ['a,b\r\nc,d'], []) + def test_read_eof(self): + self._read_test(['a,"'], [['a', '']]) + self._read_test(['"a'], [['a']]) + self._read_test(['^'], [['\n']], escapechar='^') + self.assertRaises(csv.Error, self._read_test, ['a,"'], [], strict=True) + self.assertRaises(csv.Error, self._read_test, ['"a'], [], strict=True) + self.assertRaises(csv.Error, self._read_test, + ['^'], [], escapechar='^', strict=True) + def test_read_escape(self): self._read_test(['a,\\b,c'], [['a', 'b', 'c']], escapechar='\\') self._read_test(['a,b\\,c'], [['a', 'b,c']], escapechar='\\') diff --git a/lib-python/2.7/test/test_decimal.py b/lib-python/2.7/test/test_decimal.py --- a/lib-python/2.7/test/test_decimal.py +++ b/lib-python/2.7/test/test_decimal.py @@ -1448,6 +1448,18 @@ self.assertEqual(float(d1), 66) self.assertEqual(float(d2), 15.32) + def test_nan_to_float(self): + # Test conversions of decimal NANs to float. + # See http://bugs.python.org/issue15544 + for s in ('nan', 'nan1234', '-nan', '-nan2468'): + f = float(Decimal(s)) + self.assertTrue(math.isnan(f)) + + def test_snan_to_float(self): + for s in ('snan', '-snan', 'snan1357', '-snan1234'): + d = Decimal(s) + self.assertRaises(ValueError, float, d) + def test_eval_round_trip(self): #with zero diff --git a/lib-python/2.7/test/test_deque.py b/lib-python/2.7/test/test_deque.py --- a/lib-python/2.7/test/test_deque.py +++ b/lib-python/2.7/test/test_deque.py @@ -6,6 +6,7 @@ import copy import cPickle as pickle import random +import struct BIG = 100000 @@ -517,6 +518,21 @@ gc.collect() self.assertTrue(ref() is None, "Cycle was not collected") + check_sizeof = test_support.check_sizeof + + @test_support.cpython_only + def test_sizeof(self): + BLOCKLEN = 62 + basesize = test_support.calcobjsize('2P4PlP') + blocksize = struct.calcsize('2P%dP' % BLOCKLEN) + self.assertEqual(object.__sizeof__(deque()), basesize) + check = self.check_sizeof + check(deque(), basesize + blocksize) + check(deque('a'), basesize + blocksize) + check(deque('a' * (BLOCKLEN // 2)), basesize + blocksize) + check(deque('a' * (BLOCKLEN // 2 + 1)), basesize + 2 * blocksize) + check(deque('a' * (42 * BLOCKLEN)), basesize + 43 * blocksize) + class TestVariousIteratorArgs(unittest.TestCase): def test_constructor(self): diff --git a/lib-python/2.7/test/test_descr.py b/lib-python/2.7/test/test_descr.py --- a/lib-python/2.7/test/test_descr.py +++ b/lib-python/2.7/test/test_descr.py @@ -1419,6 +1419,22 @@ self.assertEqual(x, spam.spamlist) self.assertEqual(a, a1) self.assertEqual(d, d1) + spam_cm = spam.spamlist.__dict__['classmeth'] + x2, a2, d2 = spam_cm(spam.spamlist, *a, **d) + self.assertEqual(x2, spam.spamlist) + self.assertEqual(a2, a1) + self.assertEqual(d2, d1) + class SubSpam(spam.spamlist): pass + x2, a2, d2 = spam_cm(SubSpam, *a, **d) + self.assertEqual(x2, SubSpam) + self.assertEqual(a2, a1) + self.assertEqual(d2, d1) + with self.assertRaises(TypeError): + spam_cm() + with self.assertRaises(TypeError): + spam_cm(spam.spamlist()) + with self.assertRaises(TypeError): + spam_cm(list) def test_staticmethods(self): # Testing static methods... @@ -4591,7 +4607,15 @@ pass Foo.__repr__ = Foo.__str__ foo = Foo() - str(foo) + self.assertRaises(RuntimeError, str, foo) + self.assertRaises(RuntimeError, repr, foo) + + def test_mixing_slot_wrappers(self): + class X(dict): + __setattr__ = dict.__setitem__ + x = X() + x.y = 42 + self.assertEqual(x["y"], 42) def test_cycle_through_dict(self): # See bug #1469629 diff --git a/lib-python/2.7/test/test_dict.py b/lib-python/2.7/test/test_dict.py --- a/lib-python/2.7/test/test_dict.py +++ b/lib-python/2.7/test/test_dict.py @@ -254,6 +254,14 @@ d = dict(zip(range(6), range(6))) self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6))) + class baddict3(dict): + def __new__(cls): + return d + d = {i : i for i in range(10)} + res = d.copy() + res.update(a=None, b=None, c=None) + self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res) + def test_copy(self): d = {1:1, 2:2, 3:3} self.assertEqual(d.copy(), {1:1, 2:2, 3:3}) diff --git a/lib-python/2.7/test/test_dictcomps.py b/lib-python/2.7/test/test_dictcomps.py --- a/lib-python/2.7/test/test_dictcomps.py +++ b/lib-python/2.7/test/test_dictcomps.py @@ -1,54 +1,91 @@ +import unittest -doctests = """ +from test import test_support as support - >>> k = "old value" - >>> { k: None for k in range(10) } - {0: None, 1: None, 2: None, 3: None, 4: None, 5: None, 6: None, 7: None, 8: None, 9: None} - >>> k - 'old value' +# For scope testing. +g = "Global variable" - >>> { k: k+10 for k in range(10) } - {0: 10, 1: 11, 2: 12, 3: 13, 4: 14, 5: 15, 6: 16, 7: 17, 8: 18, 9: 19} - >>> g = "Global variable" - >>> { k: g for k in range(10) } - {0: 'Global variable', 1: 'Global variable', 2: 'Global variable', 3: 'Global variable', 4: 'Global variable', 5: 'Global variable', 6: 'Global variable', 7: 'Global variable', 8: 'Global variable', 9: 'Global variable'} +class DictComprehensionTest(unittest.TestCase): - >>> { k: v for k in range(10) for v in range(10) if k == v } - {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9} + def test_basics(self): + expected = {0: 10, 1: 11, 2: 12, 3: 13, 4: 14, 5: 15, 6: 16, 7: 17, + 8: 18, 9: 19} + actual = {k: k + 10 for k in range(10)} + self.assertEqual(actual, expected) - >>> { k: v for v in range(10) for k in range(v*9, v*10) } - {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4, 38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6, 55: 6, 56: 6, 57: 6, 58: 6, 59: 6, 63: 7, 64: 7, 65: 7, 66: 7, 67: 7, 68: 7, 69: 7, 72: 8, 73: 8, 74: 8, 75: 8, 76: 8, 77: 8, 78: 8, 79: 8, 81: 9, 82: 9, 83: 9, 84: 9, 85: 9, 86: 9, 87: 9, 88: 9, 89: 9} + expected = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9} + actual = {k: v for k in range(10) for v in range(10) if k == v} + self.assertEqual(actual, expected) - >>> { x: y for y, x in ((1, 2), (3, 4)) } = 5 # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - SyntaxError: ... + def test_scope_isolation(self): + k = "Local Variable" - >>> { x: y for y, x in ((1, 2), (3, 4)) } += 5 # doctest: +IGNORE_EXCEPTION_DETAIL - Traceback (most recent call last): - ... - SyntaxError: ... + expected = {0: None, 1: None, 2: None, 3: None, 4: None, 5: None, + 6: None, 7: None, 8: None, 9: None} + actual = {k: None for k in range(10)} + self.assertEqual(actual, expected) + self.assertEqual(k, "Local Variable") -""" + expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4, + 38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6, + 55: 6, 56: 6, 57: 6, 58: 6, 59: 6, 63: 7, 64: 7, 65: 7, + 66: 7, 67: 7, 68: 7, 69: 7, 72: 8, 73: 8, 74: 8, 75: 8, + 76: 8, 77: 8, 78: 8, 79: 8, 81: 9, 82: 9, 83: 9, 84: 9, + 85: 9, 86: 9, 87: 9, 88: 9, 89: 9} + actual = {k: v for v in range(10) for k in range(v * 9, v * 10)} + self.assertEqual(k, "Local Variable") + self.assertEqual(actual, expected) -__test__ = {'doctests' : doctests} + def test_scope_isolation_from_global(self): + expected = {0: None, 1: None, 2: None, 3: None, 4: None, 5: None, + 6: None, 7: None, 8: None, 9: None} + actual = {g: None for g in range(10)} + self.assertEqual(actual, expected) + self.assertEqual(g, "Global variable") -def test_main(verbose=None): - import sys - from test import test_support - from test import test_dictcomps - test_support.run_doctest(test_dictcomps, verbose) + expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4, + 38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6, + 55: 6, 56: 6, 57: 6, 58: 6, 59: 6, 63: 7, 64: 7, 65: 7, + 66: 7, 67: 7, 68: 7, 69: 7, 72: 8, 73: 8, 74: 8, 75: 8, + 76: 8, 77: 8, 78: 8, 79: 8, 81: 9, 82: 9, 83: 9, 84: 9, + 85: 9, 86: 9, 87: 9, 88: 9, 89: 9} + actual = {g: v for v in range(10) for g in range(v * 9, v * 10)} + self.assertEqual(g, "Global variable") + self.assertEqual(actual, expected) - # verify reference counting - if verbose and hasattr(sys, "gettotalrefcount"): - import gc - counts = [None] * 5 - for i in range(len(counts)): - test_support.run_doctest(test_dictcomps, verbose) - gc.collect() - counts[i] = sys.gettotalrefcount() - print(counts) + def test_global_visibility(self): + expected = {0: 'Global variable', 1: 'Global variable', + 2: 'Global variable', 3: 'Global variable', + 4: 'Global variable', 5: 'Global variable', + 6: 'Global variable', 7: 'Global variable', + 8: 'Global variable', 9: 'Global variable'} + actual = {k: g for k in range(10)} + self.assertEqual(actual, expected) + + def test_local_visibility(self): + v = "Local variable" + expected = {0: 'Local variable', 1: 'Local variable', + 2: 'Local variable', 3: 'Local variable', + 4: 'Local variable', 5: 'Local variable', + 6: 'Local variable', 7: 'Local variable', + 8: 'Local variable', 9: 'Local variable'} + actual = {k: v for k in range(10)} + self.assertEqual(actual, expected) + self.assertEqual(v, "Local variable") + + def test_illegal_assignment(self): + with self.assertRaisesRegexp(SyntaxError, "can't assign"): + compile("{x: y for y, x in ((1, 2), (3, 4))} = 5", "", + "exec") + + with self.assertRaisesRegexp(SyntaxError, "can't assign"): + compile("{x: y for y, x in ((1, 2), (3, 4))} += 5", "", + "exec") + + +def test_main(): + support.run_unittest(__name__) if __name__ == "__main__": - test_main(verbose=True) + test_main() diff --git a/lib-python/2.7/test/test_doctest.py b/lib-python/2.7/test/test_doctest.py --- a/lib-python/2.7/test/test_doctest.py +++ b/lib-python/2.7/test/test_doctest.py @@ -2006,6 +2006,31 @@ >>> suite.run(unittest.TestResult()) + The module need not contain any doctest examples: + + >>> suite = doctest.DocTestSuite('test.sample_doctest_no_doctests') + >>> suite.run(unittest.TestResult()) + + + However, if DocTestSuite finds no docstrings, it raises an error: + + >>> try: + ... doctest.DocTestSuite('test.sample_doctest_no_docstrings') + ... except ValueError as e: + ... error = e + + >>> print(error.args[1]) + has no docstrings + + You can prevent this error by passing a DocTestFinder instance with + the `exclude_empty` keyword argument set to False: + + >>> finder = doctest.DocTestFinder(exclude_empty=False) + >>> suite = doctest.DocTestSuite('test.sample_doctest_no_docstrings', + ... test_finder=finder) + >>> suite.run(unittest.TestResult()) + + We can use the current module: >>> suite = test.sample_doctest.test_suite() @@ -2648,7 +2673,9 @@ from test import test_doctest # Ignore all warnings about the use of class Tester in this module. - deprecations = [("class Tester is deprecated", DeprecationWarning)] + deprecations = [] + if __debug__: + deprecations.append(("class Tester is deprecated", DeprecationWarning)) if sys.py3kwarning: deprecations += [("backquote not supported", SyntaxWarning), ("execfile.. not supported", DeprecationWarning)] diff --git a/lib-python/2.7/test/test_docxmlrpc.py b/lib-python/2.7/test/test_docxmlrpc.py --- a/lib-python/2.7/test/test_docxmlrpc.py +++ b/lib-python/2.7/test/test_docxmlrpc.py @@ -100,7 +100,7 @@ self.assertEqual(response.status, 200) self.assertEqual(response.getheader("Content-type"), "text/html") - # Server throws an exception if we don't start to read the data + # Server raises an exception if we don't start to read the data response.read() def test_invalid_get_response(self): diff --git a/lib-python/2.7/test/test_email.py b/lib-python/2.7/test/test_email.py --- a/lib-python/2.7/test/test_email.py +++ b/lib-python/2.7/test/test_email.py @@ -3,10 +3,12 @@ # The specific tests now live in Lib/email/test from email.test.test_email import suite +from email.test.test_email_renamed import suite as suite2 from test import test_support def test_main(): test_support.run_unittest(suite()) + test_support.run_unittest(suite2()) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_exceptions.py b/lib-python/2.7/test/test_exceptions.py --- a/lib-python/2.7/test/test_exceptions.py +++ b/lib-python/2.7/test/test_exceptions.py @@ -479,6 +479,18 @@ except AssertionError as e: self.assertEqual(str(e), "(3,)") + def test_bad_exception_clearing(self): + # See issue 16445: use of Py_XDECREF instead of Py_CLEAR in + # BaseException_set_message gave a possible way to segfault the + # interpreter. + class Nasty(str): + def __del__(message): + del e.message + + e = ValueError(Nasty("msg")) + e.args = () + del e.message + # Helper class used by TestSameStrAndUnicodeMsg class ExcWithOverriddenStr(Exception): diff --git a/lib-python/2.7/test/test_fcntl.py b/lib-python/2.7/test/test_fcntl.py --- a/lib-python/2.7/test/test_fcntl.py +++ b/lib-python/2.7/test/test_fcntl.py @@ -6,6 +6,7 @@ import os import struct import sys +import _testcapi import unittest from test.test_support import (verbose, TESTFN, unlink, run_unittest, import_module) @@ -81,6 +82,26 @@ rv = fcntl.fcntl(self.f, fcntl.F_SETLKW, lockdata) self.f.close() + def test_fcntl_bad_file(self): + class F: + def __init__(self, fn): + self.fn = fn + def fileno(self): + return self.fn + self.assertRaises(ValueError, fcntl.fcntl, -1, fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(ValueError, fcntl.fcntl, F(-1), fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(TypeError, fcntl.fcntl, 'spam', fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(TypeError, fcntl.fcntl, F('spam'), fcntl.F_SETFL, os.O_NONBLOCK) + # Issue 15989 + self.assertRaises(ValueError, fcntl.fcntl, _testcapi.INT_MAX + 1, + fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(ValueError, fcntl.fcntl, F(_testcapi.INT_MAX + 1), + fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(ValueError, fcntl.fcntl, _testcapi.INT_MIN - 1, + fcntl.F_SETFL, os.O_NONBLOCK) + self.assertRaises(ValueError, fcntl.fcntl, F(_testcapi.INT_MIN - 1), + fcntl.F_SETFL, os.O_NONBLOCK) + def test_fcntl_64_bit(self): # Issue #1309352: fcntl shouldn't fail when the third arg fits in a # C 'long' but not in a C 'int'. diff --git a/lib-python/2.7/test/test_file2k.py b/lib-python/2.7/test/test_file2k.py --- a/lib-python/2.7/test/test_file2k.py +++ b/lib-python/2.7/test/test_file2k.py @@ -2,6 +2,9 @@ import os import unittest import itertools +import select +import signal +import subprocess import time from array import array from weakref import proxy @@ -602,6 +605,148 @@ self._test_close_open_io(io_func) + at unittest.skipUnless(os.name == 'posix', 'test requires a posix system.') +class TestFileSignalEINTR(unittest.TestCase): + def _test_reading(self, data_to_write, read_and_verify_code, method_name, + universal_newlines=False): + """Generic buffered read method test harness to verify EINTR behavior. + + Also validates that Python signal handlers are run during the read. + + Args: + data_to_write: String to write to the child process for reading + before sending it a signal, confirming the signal was handled, + writing a final newline char and closing the infile pipe. + read_and_verify_code: Single "line" of code to read from a file + object named 'infile' and validate the result. This will be + executed as part of a python subprocess fed data_to_write. + method_name: The name of the read method being tested, for use in + an error message on failure. + universal_newlines: If True, infile will be opened in universal + newline mode in the child process. + """ + if universal_newlines: + # Test the \r\n -> \n conversion while we're at it. + data_to_write = data_to_write.replace('\n', '\r\n') + infile_setup_code = 'infile = os.fdopen(sys.stdin.fileno(), "rU")' + else: + infile_setup_code = 'infile = sys.stdin' + # Total pipe IO in this function is smaller than the minimum posix OS + # pipe buffer size of 512 bytes. No writer should block. + assert len(data_to_write) < 512, 'data_to_write must fit in pipe buf.' + + child_code = ( + 'import os, signal, sys ;' + 'signal.signal(' + 'signal.SIGINT, lambda s, f: sys.stderr.write("$\\n")) ;' + + infile_setup_code + ' ;' + + 'assert isinstance(infile, file) ;' + 'sys.stderr.write("Go.\\n") ;' + + read_and_verify_code) + reader_process = subprocess.Popen( + [sys.executable, '-c', child_code], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + # Wait for the signal handler to be installed. + go = reader_process.stderr.read(4) + if go != 'Go.\n': + reader_process.kill() + self.fail('Error from %s process while awaiting "Go":\n%s' % ( + method_name, go+reader_process.stderr.read())) + reader_process.stdin.write(data_to_write) + signals_sent = 0 + rlist = [] + # We don't know when the read_and_verify_code in our child is actually + # executing within the read system call we want to interrupt. This + # loop waits for a bit before sending the first signal to increase + # the likelihood of that. Implementations without correct EINTR + # and signal handling usually fail this test. + while not rlist: + rlist, _, _ = select.select([reader_process.stderr], (), (), 0.05) + reader_process.send_signal(signal.SIGINT) + # Give the subprocess time to handle it before we loop around and + # send another one. On OSX the second signal happening close to + # immediately after the first was causing the subprocess to crash + # via the OS's default SIGINT handler. + time.sleep(0.1) + signals_sent += 1 + if signals_sent > 200: + reader_process.kill() + self.fail("failed to handle signal during %s." % method_name) + # This assumes anything unexpected that writes to stderr will also + # write a newline. That is true of the traceback printing code. + signal_line = reader_process.stderr.readline() + if signal_line != '$\n': + reader_process.kill() + self.fail('Error from %s process while awaiting signal:\n%s' % ( + method_name, signal_line+reader_process.stderr.read())) + # We append a newline to our input so that a readline call can + # end on its own before the EOF is seen. + stdout, stderr = reader_process.communicate(input='\n') + if reader_process.returncode != 0: + self.fail('%s() process exited rc=%d.\nSTDOUT:\n%s\nSTDERR:\n%s' % ( + method_name, reader_process.returncode, stdout, stderr)) + + def test_readline(self, universal_newlines=False): + """file.readline must handle signals and not lose data.""" + self._test_reading( + data_to_write='hello, world!', + read_and_verify_code=( + 'line = infile.readline() ;' + 'expected_line = "hello, world!\\n" ;' + 'assert line == expected_line, (' + '"read %r expected %r" % (line, expected_line))' + ), + method_name='readline', + universal_newlines=universal_newlines) + + def test_readline_with_universal_newlines(self): + self.test_readline(universal_newlines=True) + + def test_readlines(self, universal_newlines=False): + """file.readlines must handle signals and not lose data.""" + self._test_reading( + data_to_write='hello\nworld!', + read_and_verify_code=( + 'lines = infile.readlines() ;' + 'expected_lines = ["hello\\n", "world!\\n"] ;' + 'assert lines == expected_lines, (' + '"readlines returned wrong data.\\n" ' + '"got lines %r\\nexpected %r" ' + '% (lines, expected_lines))' + ), + method_name='readlines', + universal_newlines=universal_newlines) + + def test_readlines_with_universal_newlines(self): + self.test_readlines(universal_newlines=True) + + def test_readall(self): + """Unbounded file.read() must handle signals and not lose data.""" + self._test_reading( + data_to_write='hello, world!abcdefghijklm', + read_and_verify_code=( + 'data = infile.read() ;' + 'expected_data = "hello, world!abcdefghijklm\\n";' + 'assert data == expected_data, (' + '"read %r expected %r" % (data, expected_data))' + ), + method_name='unbounded read') + + def test_readinto(self): + """file.readinto must handle signals and not lose data.""" + self._test_reading( + data_to_write='hello, world!', + read_and_verify_code=( + 'data = bytearray(50) ;' + 'num_read = infile.readinto(data) ;' + 'expected_data = "hello, world!\\n";' + 'assert data[:num_read] == expected_data, (' + '"read %r expected %r" % (data, expected_data))' + ), + method_name='readinto') + + class StdoutTests(unittest.TestCase): def test_move_stdout_on_write(self): @@ -678,7 +823,7 @@ # So get rid of it no matter what. try: run_unittest(AutoFileTests, OtherFileTests, FileSubclassTests, - FileThreadingTests, StdoutTests) + FileThreadingTests, TestFileSignalEINTR, StdoutTests) finally: if os.path.exists(TESTFN): os.unlink(TESTFN) diff --git a/lib-python/2.7/test/test_file_eintr.py b/lib-python/2.7/test/test_file_eintr.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/test_file_eintr.py @@ -0,0 +1,239 @@ +# Written to test interrupted system calls interfering with our many buffered +# IO implementations. http://bugs.python.org/issue12268 +# +# This tests the '_io' module. Similar tests for Python 2.x's older +# default file I/O implementation exist within test_file2k.py. +# +# It was suggested that this code could be merged into test_io and the tests +# made to work using the same method as the existing signal tests in test_io. +# I was unable to get single process tests using alarm or setitimer that way +# to reproduce the EINTR problems. This process based test suite reproduces +# the problems prior to the issue12268 patch reliably on Linux and OSX. +# - gregory.p.smith + +import os +import select +import signal +import subprocess +import sys +from test.test_support import run_unittest +import time +import unittest + +# Test import all of the things we're about to try testing up front. +from _io import FileIO + + + at unittest.skipUnless(os.name == 'posix', 'tests requires a posix system.') +class TestFileIOSignalInterrupt(unittest.TestCase): + def setUp(self): + self._process = None + + def tearDown(self): + if self._process and self._process.poll() is None: + try: + self._process.kill() + except OSError: + pass + + def _generate_infile_setup_code(self): + """Returns the infile = ... line of code for the reader process. + + subclasseses should override this to test different IO objects. + """ + return ('import _io ;' + 'infile = _io.FileIO(sys.stdin.fileno(), "rb")') + + def fail_with_process_info(self, why, stdout=b'', stderr=b'', + communicate=True): + """A common way to cleanup and fail with useful debug output. + + Kills the process if it is still running, collects remaining output + and fails the test with an error message including the output. + + Args: + why: Text to go after "Error from IO process" in the message. + stdout, stderr: standard output and error from the process so + far to include in the error message. + communicate: bool, when True we call communicate() on the process + after killing it to gather additional output. + """ + if self._process.poll() is None: + time.sleep(0.1) # give it time to finish printing the error. + try: + self._process.terminate() # Ensure it dies. + except OSError: + pass + if communicate: + stdout_end, stderr_end = self._process.communicate() + stdout += stdout_end + stderr += stderr_end + self.fail('Error from IO process %s:\nSTDOUT:\n%sSTDERR:\n%s\n' % + (why, stdout.decode(), stderr.decode())) + + def _test_reading(self, data_to_write, read_and_verify_code): + """Generic buffered read method test harness to validate EINTR behavior. + + Also validates that Python signal handlers are run during the read. + + Args: + data_to_write: String to write to the child process for reading + before sending it a signal, confirming the signal was handled, + writing a final newline and closing the infile pipe. + read_and_verify_code: Single "line" of code to read from a file + object named 'infile' and validate the result. This will be + executed as part of a python subprocess fed data_to_write. + """ + infile_setup_code = self._generate_infile_setup_code() + # Total pipe IO in this function is smaller than the minimum posix OS + # pipe buffer size of 512 bytes. No writer should block. + assert len(data_to_write) < 512, 'data_to_write must fit in pipe buf.' + + # Start a subprocess to call our read method while handling a signal. + self._process = subprocess.Popen( + [sys.executable, '-u', '-c', + 'import io, signal, sys ;' + 'signal.signal(signal.SIGINT, ' + 'lambda s, f: sys.stderr.write("$\\n")) ;' + + infile_setup_code + ' ;' + + 'sys.stderr.write("Worm Sign!\\n") ;' + + read_and_verify_code + ' ;' + + 'infile.close()' + ], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + # Wait for the signal handler to be installed. + worm_sign = self._process.stderr.read(len(b'Worm Sign!\n')) + if worm_sign != b'Worm Sign!\n': # See also, Dune by Frank Herbert. + self.fail_with_process_info('while awaiting a sign', + stderr=worm_sign) + self._process.stdin.write(data_to_write) + + signals_sent = 0 + rlist = [] + # We don't know when the read_and_verify_code in our child is actually + # executing within the read system call we want to interrupt. This + # loop waits for a bit before sending the first signal to increase + # the likelihood of that. Implementations without correct EINTR + # and signal handling usually fail this test. + while not rlist: + rlist, _, _ = select.select([self._process.stderr], (), (), 0.05) + self._process.send_signal(signal.SIGINT) + signals_sent += 1 + if signals_sent > 200: + self._process.kill() + self.fail('reader process failed to handle our signals.') + # This assumes anything unexpected that writes to stderr will also + # write a newline. That is true of the traceback printing code. + signal_line = self._process.stderr.readline() + if signal_line != b'$\n': + self.fail_with_process_info('while awaiting signal', + stderr=signal_line) + + # We append a newline to our input so that a readline call can + # end on its own before the EOF is seen and so that we're testing + # the read call that was interrupted by a signal before the end of + # the data stream has been reached. + stdout, stderr = self._process.communicate(input=b'\n') + if self._process.returncode: + self.fail_with_process_info( + 'exited rc=%d' % self._process.returncode, + stdout, stderr, communicate=False) + # PASS! + + # String format for the read_and_verify_code used by read methods. + _READING_CODE_TEMPLATE = ( + 'got = infile.{read_method_name}() ;' + 'expected = {expected!r} ;' + 'assert got == expected, (' + '"{read_method_name} returned wrong data.\\n"' + '"got data %r\\nexpected %r" % (got, expected))' + ) + + def test_readline(self): + """readline() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello, world!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readline', + expected=b'hello, world!\n')) + + def test_readlines(self): + """readlines() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readlines', + expected=[b'hello\n', b'world!\n'])) + + def test_readall(self): + """readall() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readall', + expected=b'hello\nworld!\n')) + # read() is the same thing as readall(). + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='read', + expected=b'hello\nworld!\n')) + + +class TestBufferedIOSignalInterrupt(TestFileIOSignalInterrupt): + def _generate_infile_setup_code(self): + """Returns the infile = ... line of code to make a BufferedReader.""" + return ('infile = io.open(sys.stdin.fileno(), "rb") ;' + 'import _io ;assert isinstance(infile, _io.BufferedReader)') + + def test_readall(self): + """BufferedReader.read() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='read', + expected=b'hello\nworld!\n')) + + +class TestTextIOSignalInterrupt(TestFileIOSignalInterrupt): + def _generate_infile_setup_code(self): + """Returns the infile = ... line of code to make a TextIOWrapper.""" + return ('infile = io.open(sys.stdin.fileno(), "rt", newline=None) ;' + 'import _io ;assert isinstance(infile, _io.TextIOWrapper)') + + def test_readline(self): + """readline() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello, world!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readline', + expected='hello, world!\n')) + + def test_readlines(self): + """readlines() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\r\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='readlines', + expected=['hello\n', 'world!\n'])) + + def test_readall(self): + """read() must handle signals and not lose data.""" + self._test_reading( + data_to_write=b'hello\nworld!', + read_and_verify_code=self._READING_CODE_TEMPLATE.format( + read_method_name='read', + expected="hello\nworld!\n")) + + +def test_main(): + test_cases = [ + tc for tc in globals().values() + if isinstance(tc, type) and issubclass(tc, unittest.TestCase)] + run_unittest(*test_cases) + + +if __name__ == '__main__': + test_main() diff --git a/lib-python/2.7/test/test_fileio.py b/lib-python/2.7/test/test_fileio.py --- a/lib-python/2.7/test/test_fileio.py +++ b/lib-python/2.7/test/test_fileio.py @@ -9,6 +9,8 @@ from array import array from weakref import proxy from functools import wraps +from UserList import UserList +import _testcapi from test.test_support import TESTFN, check_warnings, run_unittest, make_bad_fd from test.test_support import py3k_bytes as bytes @@ -71,6 +73,26 @@ n = self.f.readinto(a) self.assertEqual(array(b'b', [1, 2]), a[:n]) + def testWritelinesList(self): + l = [b'123', b'456'] + self.f.writelines(l) + self.f.close() + self.f = _FileIO(TESTFN, 'rb') + buf = self.f.read() + self.assertEqual(buf, b'123456') + + def testWritelinesUserList(self): + l = UserList([b'123', b'456']) + self.f.writelines(l) + self.f.close() + self.f = _FileIO(TESTFN, 'rb') + buf = self.f.read() + self.assertEqual(buf, b'123456') + + def testWritelinesError(self): + self.assertRaises(TypeError, self.f.writelines, [1, 2, 3]) + self.assertRaises(TypeError, self.f.writelines, None) + def test_none_args(self): self.f.write(b"hi\nbye\nabc") self.f.close() @@ -130,6 +152,14 @@ else: self.fail("Should have raised IOError") + @unittest.skipIf(os.name == 'nt', "test only works on a POSIX-like system") + def testOpenDirFD(self): + fd = os.open('.', os.O_RDONLY) + with self.assertRaises(IOError) as cm: + _FileIO(fd, 'r') + os.close(fd) + self.assertEqual(cm.exception.errno, errno.EISDIR) + #A set of functions testing that we get expected behaviour if someone has #manually closed the internal file descriptor. First, a decorator: def ClosedFD(func): @@ -314,6 +344,9 @@ if sys.platform == 'win32': import msvcrt self.assertRaises(IOError, msvcrt.get_osfhandle, make_bad_fd()) + # Issue 15989 + self.assertRaises(TypeError, _FileIO, _testcapi.INT_MAX + 1) + self.assertRaises(TypeError, _FileIO, _testcapi.INT_MIN - 1) def testBadModeArgument(self): # verify that we get a sensible error message for bad mode argument @@ -417,10 +450,22 @@ env = dict(os.environ) env[b'LC_CTYPE'] = b'C' _, out = run_python('-c', 'import _io; _io.FileIO(%r)' % filename, env=env) - if ('UnicodeEncodeError' not in out and - 'IOError: [Errno 2] No such file or directory' not in out): + if ('UnicodeEncodeError' not in out and not + ( ('IOError: [Errno 2] No such file or directory' in out) or + ('IOError: [Errno 22] Invalid argument' in out) ) ): self.fail('Bad output: %r' % out) + def testUnclosedFDOnException(self): + class MyException(Exception): pass + class MyFileIO(_FileIO): + def __setattr__(self, name, value): + if name == "name": + raise MyException("blocked setting name") + return super(MyFileIO, self).__setattr__(name, value) + fd = os.open(__file__, os.O_RDONLY) + self.assertRaises(MyException, MyFileIO, fd) + os.close(fd) # should not raise OSError(EBADF) + def test_main(): # Historically, these tests have been sloppy about removing TESTFN. # So get rid of it no matter what. diff --git a/lib-python/2.7/test/test_format.py b/lib-python/2.7/test/test_format.py --- a/lib-python/2.7/test/test_format.py +++ b/lib-python/2.7/test/test_format.py @@ -234,6 +234,16 @@ testformat('%g', 1.1, '1.1') testformat('%#g', 1.1, '1.10000') + # Regression test for http://bugs.python.org/issue15516. + class IntFails(object): + def __int__(self): + raise TestFailed + def __long__(self): + return 0 + + fst = IntFails() + testformat("%x", fst, '0') + # Test exception for unknown format characters if verbose: print 'Testing exceptions' diff --git a/lib-python/2.7/test/test_functools.py b/lib-python/2.7/test/test_functools.py --- a/lib-python/2.7/test/test_functools.py +++ b/lib-python/2.7/test/test_functools.py @@ -151,6 +151,23 @@ f_copy = pickle.loads(pickle.dumps(f)) self.assertEqual(signature(f), signature(f_copy)) + # Issue 6083: Reference counting bug + def test_setstate_refcount(self): + class BadSequence: + def __len__(self): + return 4 + def __getitem__(self, key): + if key == 0: + return max + elif key == 1: + return tuple(range(1000000)) + elif key in (2, 3): + return {} + raise IndexError + + f = self.thetype(object) + self.assertRaises(SystemError, f.__setstate__, BadSequence()) + class PartialSubclass(functools.partial): pass @@ -164,6 +181,7 @@ # the python version isn't picklable def test_pickle(self): pass + def test_setstate_refcount(self): pass class TestUpdateWrapper(unittest.TestCase): @@ -232,6 +250,7 @@ self.assertEqual(wrapper.attr, 'This is a different test') self.assertEqual(wrapper.dict_attr, f.dict_attr) + @test_support.requires_docstrings def test_builtin_update(self): # Test for bug #1576241 def wrapper(): @@ -258,7 +277,7 @@ self.assertEqual(wrapper.__name__, 'f') self.assertEqual(wrapper.attr, 'This is also a test') - @unittest.skipIf(not sys.flags.optimize <= 1, + @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_default_update_doc(self): wrapper = self._default_update() diff --git a/lib-python/2.7/test/test_gc.py b/lib-python/2.7/test/test_gc.py --- a/lib-python/2.7/test/test_gc.py +++ b/lib-python/2.7/test/test_gc.py @@ -1,9 +1,15 @@ import unittest from test.test_support import verbose, run_unittest import sys +import time import gc import weakref +try: + import threading +except ImportError: + threading = None + ### Support code ############################################################################### @@ -299,6 +305,69 @@ v = {1: v, 2: Ouch()} gc.disable() + @unittest.skipUnless(threading, "test meaningless on builds without threads") + def test_trashcan_threads(self): + # Issue #13992: trashcan mechanism should be thread-safe + NESTING = 60 + N_THREADS = 2 + + def sleeper_gen(): + """A generator that releases the GIL when closed or dealloc'ed.""" + try: + yield + finally: + time.sleep(0.000001) + + class C(list): + # Appending to a list is atomic, which avoids the use of a lock. + inits = [] + dels = [] + def __init__(self, alist): + self[:] = alist + C.inits.append(None) + def __del__(self): + # This __del__ is called by subtype_dealloc(). + C.dels.append(None) + # `g` will release the GIL when garbage-collected. This + # helps assert subtype_dealloc's behaviour when threads + # switch in the middle of it. + g = sleeper_gen() + next(g) + # Now that __del__ is finished, subtype_dealloc will proceed + # to call list_dealloc, which also uses the trashcan mechanism. + + def make_nested(): + """Create a sufficiently nested container object so that the + trashcan mechanism is invoked when deallocating it.""" + x = C([]) + for i in range(NESTING): + x = [C([x])] + del x + + def run_thread(): + """Exercise make_nested() in a loop.""" + while not exit: + make_nested() + + old_checkinterval = sys.getcheckinterval() + sys.setcheckinterval(3) + try: + exit = False + threads = [] + for i in range(N_THREADS): + t = threading.Thread(target=run_thread) + threads.append(t) + for t in threads: + t.start() + time.sleep(1.0) + exit = True + for t in threads: + t.join() + finally: + sys.setcheckinterval(old_checkinterval) + gc.collect() + self.assertEqual(len(C.inits), len(C.dels)) + def test_boom(self): class Boom: def __getattr__(self, someattribute): diff --git a/lib-python/2.7/test/test_gdb.py b/lib-python/2.7/test/test_gdb.py --- a/lib-python/2.7/test/test_gdb.py +++ b/lib-python/2.7/test/test_gdb.py @@ -19,19 +19,48 @@ # This is what "no gdb" looks like. There may, however, be other # errors that manifest this way too. raise unittest.SkipTest("Couldn't find gdb on the path") -gdb_version_number = re.search(r"^GNU gdb [^\d]*(\d+)\.", gdb_version) -if int(gdb_version_number.group(1)) < 7: +gdb_version_number = re.search("^GNU gdb [^\d]*(\d+)\.(\d)", gdb_version) +gdb_major_version = int(gdb_version_number.group(1)) +gdb_minor_version = int(gdb_version_number.group(2)) +if gdb_major_version < 7: raise unittest.SkipTest("gdb versions before 7.0 didn't support python embedding" " Saw:\n" + gdb_version) +# Location of custom hooks file in a repository checkout. +checkout_hook_path = os.path.join(os.path.dirname(sys.executable), + 'python-gdb.py') + +def run_gdb(*args, **env_vars): + """Runs gdb in --batch mode with the additional arguments given by *args. + + Returns its (stdout, stderr) + """ + if env_vars: + env = os.environ.copy() + env.update(env_vars) + else: + env = None + base_cmd = ('gdb', '--batch') + if (gdb_major_version, gdb_minor_version) >= (7, 4): + base_cmd += ('-iex', 'add-auto-load-safe-path ' + checkout_hook_path) + out, err = subprocess.Popen(base_cmd + args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, + ).communicate() + return out, err + # Verify that "gdb" was built with the embedded python support enabled: -cmd = "--eval-command=python import sys; print sys.version_info" -p = subprocess.Popen(["gdb", "--batch", cmd], - stdout=subprocess.PIPE) -gdbpy_version, _ = p.communicate() -if gdbpy_version == '': +gdbpy_version, _ = run_gdb("--eval-command=python import sys; print sys.version_info") +if not gdbpy_version: raise unittest.SkipTest("gdb not built with embedded python support") +# Verify that "gdb" can load our custom hooks. In theory this should never +# fail, but we don't handle the case of the hooks file not existing if the +# tests are run from an installed Python (we'll produce failures in that case). +cmd = ['--args', sys.executable] +_, gdbpy_errors = run_gdb('--args', sys.executable) +if "auto-loading has been declined" in gdbpy_errors: + msg = "gdb security settings prevent use of custom hooks: " + def python_is_optimized(): cflags = sysconfig.get_config_vars()['PY_CFLAGS'] final_opt = "" @@ -42,10 +71,7 @@ def gdb_has_frame_select(): # Does this build of gdb have gdb.Frame.select ? - cmd = "--eval-command=python print(dir(gdb.Frame))" - p = subprocess.Popen(["gdb", "--batch", cmd], - stdout=subprocess.PIPE) - stdout, _ = p.communicate() + stdout, _ = run_gdb("--eval-command=python print(dir(gdb.Frame))") m = re.match(r'.*\[(.*)\].*', stdout) if not m: raise unittest.SkipTest("Unable to parse output from gdb.Frame.select test") @@ -58,21 +84,6 @@ """Test that the debugger can debug Python.""" - def run_gdb(self, *args, **env_vars): - """Runs gdb with the command line given by *args. - - Returns its stdout, stderr - """ - if env_vars: - env = os.environ.copy() - env.update(env_vars) - else: - env = None - out, err = subprocess.Popen( - args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env - ).communicate() - return out, err - def get_stack_trace(self, source=None, script=None, breakpoint='PyObject_Print', cmds_after_breakpoint=None, @@ -129,7 +140,7 @@ # print ' '.join(args) # Use "args" to invoke gdb, capturing stdout, stderr: - out, err = self.run_gdb(*args, PYTHONHASHSEED='0') + out, err = run_gdb(*args, PYTHONHASHSEED='0') # Ignore some noise on stderr due to the pending breakpoint: err = err.replace('Function "%s" not defined.\n' % breakpoint, '') @@ -141,6 +152,16 @@ err = err.replace("warning: Cannot initialize thread debugging" " library: Debugger service failed\n", '') + err = err.replace('warning: Could not load shared library symbols for ' + 'linux-vdso.so.1.\n' + 'Do you need "set solib-search-path" or ' + '"set sysroot"?\n', + '') + err = err.replace('warning: Could not load shared library symbols for ' + 'linux-gate.so.1.\n' + 'Do you need "set solib-search-path" or ' + '"set sysroot"?\n', + '') # Ensure no unexpected error messages: self.assertEqual(err, '') diff --git a/lib-python/2.7/test/test_generators.py b/lib-python/2.7/test/test_generators.py --- a/lib-python/2.7/test/test_generators.py +++ b/lib-python/2.7/test/test_generators.py @@ -383,7 +383,8 @@ >>> [s for s in dir(i) if not s.startswith('_')] ['close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw'] ->>> print i.next.__doc__ +>>> from test.test_support import HAVE_DOCSTRINGS +>>> print(i.next.__doc__ if HAVE_DOCSTRINGS else 'x.next() -> the next value, or raise StopIteration') x.next() -> the next value, or raise StopIteration >>> iter(i) is i True diff --git a/lib-python/2.7/test/test_genexps.py b/lib-python/2.7/test/test_genexps.py --- a/lib-python/2.7/test/test_genexps.py +++ b/lib-python/2.7/test/test_genexps.py @@ -223,7 +223,8 @@ >>> set(attr for attr in dir(g) if not attr.startswith('__')) >= expected True - >>> print g.next.__doc__ + >>> from test.test_support import HAVE_DOCSTRINGS + >>> print(g.next.__doc__ if HAVE_DOCSTRINGS else 'x.next() -> the next value, or raise StopIteration') x.next() -> the next value, or raise StopIteration >>> import types >>> isinstance(g, types.GeneratorType) diff --git a/lib-python/2.7/test/test_glob.py b/lib-python/2.7/test/test_glob.py --- a/lib-python/2.7/test/test_glob.py +++ b/lib-python/2.7/test/test_glob.py @@ -1,8 +1,15 @@ -import unittest -from test.test_support import run_unittest, TESTFN import glob import os import shutil +import sys +import unittest + +from test.test_support import run_unittest, TESTFN + + +def fsdecode(s): + return unicode(s, sys.getfilesystemencoding()) + class GlobTests(unittest.TestCase): @@ -18,16 +25,19 @@ f.close() def setUp(self): - self.tempdir = TESTFN+"_dir" + self.tempdir = TESTFN + "_dir" self.mktemp('a', 'D') self.mktemp('aab', 'F') + self.mktemp('.aa', 'G') + self.mktemp('.bb', 'H') self.mktemp('aaa', 'zzzF') self.mktemp('ZZZ') self.mktemp('a', 'bcd', 'EF') self.mktemp('a', 'bcd', 'efg', 'ha') if hasattr(os, 'symlink'): os.symlink(self.norm('broken'), self.norm('sym1')) - os.symlink(self.norm('broken'), self.norm('sym2')) + os.symlink('broken', self.norm('sym2')) + os.symlink(os.path.join('a', 'bcd'), self.norm('sym3')) def tearDown(self): shutil.rmtree(self.tempdir) @@ -40,10 +50,16 @@ p = os.path.join(self.tempdir, pattern) res = glob.glob(p) self.assertEqual(list(glob.iglob(p)), res) + ures = [fsdecode(x) for x in res] + self.assertEqual(glob.glob(fsdecode(p)), ures) + self.assertEqual(list(glob.iglob(fsdecode(p))), ures) return res def assertSequencesEqual_noorder(self, l1, l2): + l1 = list(l1) + l2 = list(l2) self.assertEqual(set(l1), set(l2)) + self.assertEqual(sorted(l1), sorted(l2)) def test_glob_literal(self): eq = self.assertSequencesEqual_noorder @@ -52,20 +68,26 @@ eq(self.glob('aab'), [self.norm('aab')]) eq(self.glob('zymurgy'), []) + res = glob.glob('*') + self.assertEqual({type(r) for r in res}, {str}) + res = glob.glob(os.path.join(os.curdir, '*')) + self.assertEqual({type(r) for r in res}, {str}) + # test return types are unicode, but only if os.listdir # returns unicode filenames - uniset = set([unicode]) - tmp = os.listdir(u'.') - if set(type(x) for x in tmp) == uniset: - u1 = glob.glob(u'*') - u2 = glob.glob(u'./*') - self.assertEqual(set(type(r) for r in u1), uniset) - self.assertEqual(set(type(r) for r in u2), uniset) + tmp = os.listdir(fsdecode(os.curdir)) + if {type(x) for x in tmp} == {unicode}: + res = glob.glob(u'*') + self.assertEqual({type(r) for r in res}, {unicode}) + res = glob.glob(os.path.join(fsdecode(os.curdir), u'*')) + self.assertEqual({type(r) for r in res}, {unicode}) def test_glob_one_directory(self): eq = self.assertSequencesEqual_noorder eq(self.glob('a*'), map(self.norm, ['a', 'aab', 'aaa'])) eq(self.glob('*a'), map(self.norm, ['a', 'aaa'])) + eq(self.glob('.*'), map(self.norm, ['.aa', '.bb'])) + eq(self.glob('?aa'), map(self.norm, ['aaa'])) eq(self.glob('aa?'), map(self.norm, ['aaa', 'aab'])) eq(self.glob('aa[ab]'), map(self.norm, ['aaa', 'aab'])) eq(self.glob('*q'), []) @@ -87,23 +109,68 @@ eq(self.glob('*', '*a'), []) eq(self.glob('a', '*', '*', '*a'), [self.norm('a', 'bcd', 'efg', 'ha')]) - eq(self.glob('?a?', '*F'), map(self.norm, [os.path.join('aaa', 'zzzF'), - os.path.join('aab', 'F')])) + eq(self.glob('?a?', '*F'), [self.norm('aaa', 'zzzF'), + self.norm('aab', 'F')]) def test_glob_directory_with_trailing_slash(self): - # We are verifying that when there is wildcard pattern which - # ends with os.sep doesn't blow up. - res = glob.glob(self.tempdir + '*' + os.sep) - self.assertEqual(len(res), 1) - # either of these results are reasonable - self.assertIn(res[0], [self.tempdir, self.tempdir + os.sep]) + # Patterns ending with a slash shouldn't match non-dirs + res = glob.glob(self.norm('Z*Z') + os.sep) + self.assertEqual(res, []) + res = glob.glob(self.norm('ZZZ') + os.sep) + self.assertEqual(res, []) + # When there is a wildcard pattern which ends with os.sep, glob() + # doesn't blow up. + res = glob.glob(self.norm('aa*') + os.sep) + self.assertEqual(len(res), 2) + # either of these results is reasonable + self.assertIn(set(res), [ + {self.norm('aaa'), self.norm('aab')}, + {self.norm('aaa') + os.sep, self.norm('aab') + os.sep}, + ]) + def test_glob_unicode_directory_with_trailing_slash(self): + # Same as test_glob_directory_with_trailing_slash, but with an + # unicode argument. + res = glob.glob(fsdecode(self.norm('Z*Z') + os.sep)) + self.assertEqual(res, []) + res = glob.glob(fsdecode(self.norm('ZZZ') + os.sep)) + self.assertEqual(res, []) + res = glob.glob(fsdecode(self.norm('aa*') + os.sep)) + self.assertEqual(len(res), 2) + # either of these results is reasonable + self.assertIn(set(res), [ + {fsdecode(self.norm('aaa')), fsdecode(self.norm('aab'))}, + {fsdecode(self.norm('aaa') + os.sep), + fsdecode(self.norm('aab') + os.sep)}, + ]) + + @unittest.skipUnless(hasattr(os, 'symlink'), "Requires symlink support") + def test_glob_symlinks(self): + eq = self.assertSequencesEqual_noorder + eq(self.glob('sym3'), [self.norm('sym3')]) + eq(self.glob('sym3', '*'), [self.norm('sym3', 'EF'), + self.norm('sym3', 'efg')]) + self.assertIn(self.glob('sym3' + os.sep), + [[self.norm('sym3')], [self.norm('sym3') + os.sep]]) + eq(self.glob('*', '*F'), + [self.norm('aaa', 'zzzF'), self.norm('aab', 'F'), + self.norm('sym3', 'EF')]) + + @unittest.skipUnless(hasattr(os, 'symlink'), "Requires symlink support") def test_glob_broken_symlinks(self): - if hasattr(os, 'symlink'): - eq = self.assertSequencesEqual_noorder - eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2')]) - eq(self.glob('sym1'), [self.norm('sym1')]) - eq(self.glob('sym2'), [self.norm('sym2')]) + eq = self.assertSequencesEqual_noorder + eq(self.glob('sym*'), [self.norm('sym1'), self.norm('sym2'), + self.norm('sym3')]) + eq(self.glob('sym1'), [self.norm('sym1')]) + eq(self.glob('sym2'), [self.norm('sym2')]) + + @unittest.skipUnless(sys.platform == "win32", "Win32 specific test") + def test_glob_magic_in_drive(self): + eq = self.assertSequencesEqual_noorder + eq(glob.glob('*:'), []) + eq(glob.glob(u'*:'), []) + eq(glob.glob('?:'), []) + eq(glob.glob(u'?:'), []) def test_main(): diff --git a/lib-python/2.7/test/test_gzip.py b/lib-python/2.7/test/test_gzip.py --- a/lib-python/2.7/test/test_gzip.py +++ b/lib-python/2.7/test/test_gzip.py @@ -53,6 +53,13 @@ d = f.read() self.assertEqual(d, data1*50) + def test_read_universal_newlines(self): + # Issue #5148: Reading breaks when mode contains 'U'. + self.test_write() + with gzip.GzipFile(self.filename, 'rU') as f: + d = f.read() + self.assertEqual(d, data1*50) + def test_io_on_closed_object(self): # Test that I/O operations on closed GzipFile objects raise a # ValueError, just like the corresponding functions on file objects. @@ -282,6 +289,24 @@ with gzip.GzipFile(fileobj=f, mode="w") as g: self.assertEqual(g.name, "") + def test_read_truncated(self): + data = data1*50 + buf = io.BytesIO() + with gzip.GzipFile(fileobj=buf, mode="w") as f: + f.write(data) + # Drop the CRC (4 bytes) and file size (4 bytes). + truncated = buf.getvalue()[:-8] + with gzip.GzipFile(fileobj=io.BytesIO(truncated)) as f: + self.assertRaises(EOFError, f.read) + with gzip.GzipFile(fileobj=io.BytesIO(truncated)) as f: + self.assertEqual(f.read(len(data)), data) + self.assertRaises(EOFError, f.read, 1) + # Incomplete 10-byte header. + for i in range(2, 10): + with gzip.GzipFile(fileobj=io.BytesIO(truncated[:i])) as f: + self.assertRaises(EOFError, f.read, 1) + + def test_main(verbose=None): test_support.run_unittest(TestGzip) diff --git a/lib-python/2.7/test/test_hashlib.py b/lib-python/2.7/test/test_hashlib.py --- a/lib-python/2.7/test/test_hashlib.py +++ b/lib-python/2.7/test/test_hashlib.py @@ -108,12 +108,8 @@ _algo.islower()])) def test_unknown_hash(self): - try: - hashlib.new('spam spam spam spam spam') - except ValueError: - pass - else: - self.assertTrue(0 == "hashlib didn't reject bogus hash name") + self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam') + self.assertRaises(TypeError, hashlib.new, 1) def test_get_builtin_constructor(self): get_builtin_constructor = hashlib.__dict__[ @@ -132,6 +128,7 @@ sys.modules['_md5'] = _md5 else: del sys.modules['_md5'] + self.assertRaises(TypeError, get_builtin_constructor, 3) def test_hexdigest(self): for name in self.supported_hash_names: @@ -170,6 +167,21 @@ % (name, hash_object_constructor, computed, len(data), digest)) + def check_update(self, name, data, digest): + constructors = self.constructors_to_test[name] + # 2 is for hashlib.name(...) and hashlib.new(name, ...) + self.assertGreaterEqual(len(constructors), 2) + for hash_object_constructor in constructors: + h = hash_object_constructor() + h.update(data) + computed = h.hexdigest() + self.assertEqual( + computed, digest, + "Hash algorithm %s using %s when updated returned hexdigest" + " %r for %d byte input data that should have hashed to %r." + % (name, hash_object_constructor, + computed, len(data), digest)) + def check_unicode(self, algorithm_name): # Unicode objects are not allowed as input. expected = hashlib.new(algorithm_name, str(u'spam')).hexdigest() @@ -203,6 +215,15 @@ except OverflowError: pass # 32-bit arch + @precisionbigmemtest(size=_4G + 5, memuse=1) + def test_case_md5_huge_update(self, size): + if size == _4G + 5: + try: + self.check_update('md5', 'A'*size, + 'c9af2dff37468ce5dfee8f2cfc0a9c6d') + except OverflowError: + pass # 32-bit arch + @precisionbigmemtest(size=_4G - 1, memuse=1) def test_case_md5_uintmax(self, size): if size == _4G - 1: @@ -231,6 +252,23 @@ self.check('sha1', "a" * 1000000, "34aa973cd4c4daa4f61eeb2bdbad27316534016f") + @precisionbigmemtest(size=_4G + 5, memuse=1) + def test_case_sha1_huge(self, size): + if size == _4G + 5: + try: + self.check('sha1', 'A'*size, + '87d745c50e6b2879ffa0fb2c930e9fbfe0dc9a5b') + except OverflowError: + pass # 32-bit arch + + @precisionbigmemtest(size=_4G + 5, memuse=1) + def test_case_sha1_huge_update(self, size): + if size == _4G + 5: + try: + self.check_update('sha1', 'A'*size, + '87d745c50e6b2879ffa0fb2c930e9fbfe0dc9a5b') + except OverflowError: + pass # 32-bit arch # use the examples from Federal Information Processing Standards # Publication 180-2, Secure Hash Standard, 2002 August 1 diff --git a/lib-python/2.7/test/test_heapq.py b/lib-python/2.7/test/test_heapq.py --- a/lib-python/2.7/test/test_heapq.py +++ b/lib-python/2.7/test/test_heapq.py @@ -315,6 +315,16 @@ 'Test multiple tiers of iterators' return chain(imap(lambda x:x, R(Ig(G(seqn))))) +class SideEffectLT: + def __init__(self, value, heap): + self.value = value + self.heap = heap + + def __lt__(self, other): + self.heap[:] = [] + return self.value < other.value + + class TestErrorHandling(TestCase): module = None @@ -361,6 +371,22 @@ self.assertRaises(TypeError, f, 2, N(s)) self.assertRaises(ZeroDivisionError, f, 2, E(s)) + # Issue #17278: the heap may change size while it's being walked. + + def test_heappush_mutating_heap(self): + heap = [] + heap.extend(SideEffectLT(i, heap) for i in range(200)) + # Python version raises IndexError, C version RuntimeError + with self.assertRaises((IndexError, RuntimeError)): + self.module.heappush(heap, SideEffectLT(5, heap)) + + def test_heappop_mutating_heap(self): + heap = [] + heap.extend(SideEffectLT(i, heap) for i in range(200)) + # Python version raises IndexError, C version RuntimeError + with self.assertRaises((IndexError, RuntimeError)): + self.module.heappop(heap) + class TestErrorHandlingPython(TestErrorHandling): module = py_heapq diff --git a/lib-python/2.7/test/test_htmlparser.py b/lib-python/2.7/test/test_htmlparser.py --- a/lib-python/2.7/test/test_htmlparser.py +++ b/lib-python/2.7/test/test_htmlparser.py @@ -260,6 +260,16 @@ ('starttag', 'a', [('foo', None), ('=', None), ('bar', None)]) ] self._run_check(html, expected) + #see issue #14538 + html = ('' + '') + expected = [ + ('starttag', 'meta', []), ('starttag', 'meta', []), + ('starttag', 'meta', []), ('starttag', 'meta', []), + ('startendtag', 'meta', []), ('startendtag', 'meta', []), + ('startendtag', 'meta', []), ('startendtag', 'meta', []), + ] + self._run_check(html, expected) def test_declaration_junk_chars(self): self._run_check("", [('decl', 'DOCTYPE foo $ ')]) diff --git a/lib-python/2.7/test/test_httplib.py b/lib-python/2.7/test/test_httplib.py --- a/lib-python/2.7/test/test_httplib.py +++ b/lib-python/2.7/test/test_httplib.py @@ -90,6 +90,34 @@ conn.request('POST', '/', body, headers) self.assertEqual(conn._buffer.count[header.lower()], 1) + def test_content_length_0(self): + + class ContentLengthChecker(list): + def __init__(self): + list.__init__(self) + self.content_length = None + def append(self, item): + kv = item.split(':', 1) + if len(kv) > 1 and kv[0].lower() == 'content-length': + self.content_length = kv[1].strip() + list.append(self, item) + + # POST with empty body + conn = httplib.HTTPConnection('example.com') + conn.sock = FakeSocket(None) + conn._buffer = ContentLengthChecker() + conn.request('POST', '/', '') + self.assertEqual(conn._buffer.content_length, '0', + 'Header Content-Length not set') + + # PUT request with empty body + conn = httplib.HTTPConnection('example.com') + conn.sock = FakeSocket(None) + conn._buffer = ContentLengthChecker() + conn.request('PUT', '/', '') + self.assertEqual(conn._buffer.content_length, '0', + 'Header Content-Length not set') + def test_putheader(self): conn = httplib.HTTPConnection('example.com') conn.sock = FakeSocket(None) @@ -138,7 +166,7 @@ self.assertEqual(repr(exc), '''BadStatusLine("\'\'",)''') def test_partial_reads(self): - # if we have a lenght, the system knows when to close itself + # if we have a length, the system knows when to close itself # same behaviour than when we read the whole thing with read() body = "HTTP/1.1 200 Ok\r\nContent-Length: 4\r\n\r\nText" sock = FakeSocket(body) @@ -149,6 +177,32 @@ self.assertEqual(resp.read(2), 'xt') self.assertTrue(resp.isclosed()) + def test_partial_reads_no_content_length(self): + # when no length is present, the socket should be gracefully closed when + # all data was read + body = "HTTP/1.1 200 Ok\r\n\r\nText" + sock = FakeSocket(body) + resp = httplib.HTTPResponse(sock) + resp.begin() + self.assertEqual(resp.read(2), 'Te') + self.assertFalse(resp.isclosed()) + self.assertEqual(resp.read(2), 'xt') + self.assertEqual(resp.read(1), '') + self.assertTrue(resp.isclosed()) + + def test_partial_reads_incomplete_body(self): + # if the server shuts down the connection before the whole + # content-length is delivered, the socket is gracefully closed + body = "HTTP/1.1 200 Ok\r\nContent-Length: 10\r\n\r\nText" + sock = FakeSocket(body) + resp = httplib.HTTPResponse(sock) + resp.begin() + self.assertEqual(resp.read(2), 'Te') + self.assertFalse(resp.isclosed()) + self.assertEqual(resp.read(2), 'xt') + self.assertEqual(resp.read(1), '') + self.assertTrue(resp.isclosed()) + def test_host_port(self): # Check invalid host_port @@ -279,7 +333,7 @@ resp = httplib.HTTPResponse(sock, method="GET") resp.begin() self.assertEqual(resp.read(), 'Hello\r\n') - resp.close() + self.assertTrue(resp.isclosed()) def test_incomplete_read(self): sock = FakeSocket('HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\nHello\r\n') @@ -293,10 +347,9 @@ "IncompleteRead(7 bytes read, 3 more expected)") self.assertEqual(str(i), "IncompleteRead(7 bytes read, 3 more expected)") + self.assertTrue(resp.isclosed()) else: self.fail('IncompleteRead expected') - finally: - resp.close() def test_epipe(self): sock = EPipeSocket( @@ -349,6 +402,14 @@ resp.begin() self.assertRaises(httplib.LineTooLong, resp.read) + def test_early_eof(self): + # Test httpresponse with no \r\n termination, + body = "HTTP/1.1 200 Ok" + sock = FakeSocket(body) + resp = httplib.HTTPResponse(sock) + resp.begin() + self.assertEqual(resp.read(), '') + self.assertTrue(resp.isclosed()) class OfflineTest(TestCase): def test_responses(self): diff --git a/lib-python/2.7/test/test_httpservers.py b/lib-python/2.7/test/test_httpservers.py --- a/lib-python/2.7/test/test_httpservers.py +++ b/lib-python/2.7/test/test_httpservers.py @@ -4,11 +4,6 @@ Josip Dzolonga, and Michael Otteneder for the 2007/08 GHOP contest. """ -from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer -from SimpleHTTPServer import SimpleHTTPRequestHandler -from CGIHTTPServer import CGIHTTPRequestHandler -import CGIHTTPServer - import os import sys import re @@ -17,12 +12,17 @@ import urllib import httplib import tempfile +import unittest +import CGIHTTPServer -import unittest +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +from SimpleHTTPServer import SimpleHTTPRequestHandler +from CGIHTTPServer import CGIHTTPRequestHandler from StringIO import StringIO +from test import test_support -from test import test_support + threading = test_support.import_module('threading') @@ -43,7 +43,7 @@ self.end_headers() self.wfile.write(b'Data\r\n') - def log_message(self, format, *args): + def log_message(self, fmt, *args): pass @@ -97,9 +97,9 @@ self.handler = SocketlessRequestHandler() def send_typical_request(self, message): - input = StringIO(message) + input_msg = StringIO(message) output = StringIO() - self.handler.rfile = input + self.handler.rfile = input_msg self.handler.wfile = output self.handler.handle_one_request() output.seek(0) @@ -296,7 +296,7 @@ os.chdir(self.cwd) try: shutil.rmtree(self.tempdir) - except: + except OSError: pass finally: BaseTestCase.tearDown(self) @@ -418,42 +418,44 @@ finally: BaseTestCase.tearDown(self) - def test_url_collapse_path_split(self): + def test_url_collapse_path(self): + # verify tail is the last portion and head is the rest on proper urls test_vectors = { - '': ('/', ''), + '': '//', '..': IndexError, '/.//..': IndexError, - '/': ('/', ''), - '//': ('/', ''), - '/\\': ('/', '\\'), - '/.//': ('/', ''), - 'cgi-bin/file1.py': ('/cgi-bin', 'file1.py'), - '/cgi-bin/file1.py': ('/cgi-bin', 'file1.py'), - '/cgi-bin/file1.py/PATH-INFO': ('/cgi-bin', 'file1.py/PATH-INFO'), - 'a': ('/', 'a'), - '/a': ('/', 'a'), - '//a': ('/', 'a'), - './a': ('/', 'a'), - './C:/': ('/C:', ''), - '/a/b': ('/a', 'b'), - '/a/b/': ('/a/b', ''), - '/a/b/c/..': ('/a/b', ''), - '/a/b/c/../d': ('/a/b', 'd'), - '/a/b/c/../d/e/../f': ('/a/b/d', 'f'), - '/a/b/c/../d/e/../../f': ('/a/b', 'f'), - '/a/b/c/../d/e/.././././..//f': ('/a/b', 'f'), + '/': '//', + '//': '//', + '/\\': '//\\', + '/.//': '//', + 'cgi-bin/file1.py': '/cgi-bin/file1.py', + '/cgi-bin/file1.py': '/cgi-bin/file1.py', + 'a': '//a', + '/a': '//a', + '//a': '//a', + './a': '//a', + './C:/': '/C:/', + '/a/b': '/a/b', + '/a/b/': '/a/b/', + '/a/b/.': '/a/b/', + '/a/b/c/..': '/a/b/', + '/a/b/c/../d': '/a/b/d', + '/a/b/c/../d/e/../f': '/a/b/d/f', + '/a/b/c/../d/e/../../f': '/a/b/f', + '/a/b/c/../d/e/.././././..//f': '/a/b/f', '../a/b/c/../d/e/.././././..//f': IndexError, - '/a/b/c/../d/e/../../../f': ('/a', 'f'), - '/a/b/c/../d/e/../../../../f': ('/', 'f'), + '/a/b/c/../d/e/../../../f': '/a/f', + '/a/b/c/../d/e/../../../../f': '//f', '/a/b/c/../d/e/../../../../../f': IndexError, - '/a/b/c/../d/e/../../../../f/..': ('/', ''), + '/a/b/c/../d/e/../../../../f/..': '//', + '/a/b/c/../d/e/../../../../f/../.': '//', } for path, expected in test_vectors.iteritems(): if isinstance(expected, type) and issubclass(expected, Exception): self.assertRaises(expected, - CGIHTTPServer._url_collapse_path_split, path) + CGIHTTPServer._url_collapse_path, path) else: - actual = CGIHTTPServer._url_collapse_path_split(path) + actual = CGIHTTPServer._url_collapse_path(path) self.assertEqual(expected, actual, msg='path = %r\nGot: %r\nWanted: %r' % (path, actual, expected)) diff --git a/lib-python/2.7/test/test_imaplib.py b/lib-python/2.7/test/test_imaplib.py --- a/lib-python/2.7/test/test_imaplib.py +++ b/lib-python/2.7/test/test_imaplib.py @@ -79,7 +79,7 @@ return line += part except IOError: - # ..but SSLSockets throw exceptions. + # ..but SSLSockets raise exceptions. return if line.endswith('\r\n'): break diff --git a/lib-python/2.7/test/test_import.py b/lib-python/2.7/test/test_import.py --- a/lib-python/2.7/test/test_import.py +++ b/lib-python/2.7/test/test_import.py @@ -5,6 +5,7 @@ import py_compile import random import stat +import struct import sys import unittest import textwrap @@ -15,12 +16,23 @@ from test import symlink_support from test import script_helper +def _files(name): + return (name + os.extsep + "py", + name + os.extsep + "pyc", + name + os.extsep + "pyo", + name + os.extsep + "pyw", + name + "$py.class") + +def chmod_files(name): + for f in _files(name): + try: + os.chmod(f, 0600) + except OSError as exc: + if exc.errno != errno.ENOENT: + raise + def remove_files(name): - for f in (name + os.extsep + "py", - name + os.extsep + "pyc", - name + os.extsep + "pyo", - name + os.extsep + "pyw", - name + "$py.class"): + for f in _files(name): unlink(f) @@ -120,6 +132,40 @@ unload(TESTFN) del sys.path[0] + def test_rewrite_pyc_with_read_only_source(self): + # Issue 6074: a long time ago on posix, and more recently on Windows, + # a read only source file resulted in a read only pyc file, which + # led to problems with updating it later + sys.path.insert(0, os.curdir) + fname = TESTFN + os.extsep + "py" + try: + # Write a Python file, make it read-only and import it + with open(fname, 'w') as f: + f.write("x = 'original'\n") + # Tweak the mtime of the source to ensure pyc gets updated later + s = os.stat(fname) + os.utime(fname, (s.st_atime, s.st_mtime-100000000)) + os.chmod(fname, 0400) + m1 = __import__(TESTFN) + self.assertEqual(m1.x, 'original') + # Change the file and then reimport it + os.chmod(fname, 0600) + with open(fname, 'w') as f: + f.write("x = 'rewritten'\n") + unload(TESTFN) + m2 = __import__(TESTFN) + self.assertEqual(m2.x, 'rewritten') + # Now delete the source file and check the pyc was rewritten + unlink(fname) + unload(TESTFN) + m3 = __import__(TESTFN) + self.assertEqual(m3.x, 'rewritten') + finally: + chmod_files(TESTFN) + remove_files(TESTFN) + unload(TESTFN) + del sys.path[0] + def test_imp_module(self): # Verify that the imp module can correctly load and find .py files @@ -305,6 +351,46 @@ del sys.path[0] remove_files(TESTFN) + def test_pyc_mtime(self): + # Test for issue #13863: .pyc timestamp sometimes incorrect on Windows. + sys.path.insert(0, os.curdir) + try: + # Jan 1, 2012; Jul 1, 2012. + mtimes = 1325376000, 1341100800 + + # Different names to avoid running into import caching. + tails = "spam", "eggs" + for mtime, tail in zip(mtimes, tails): + module = TESTFN + tail + source = module + ".py" + compiled = source + ('c' if __debug__ else 'o') + + # Create a new Python file with the given mtime. + with open(source, 'w') as f: + f.write("# Just testing\nx=1, 2, 3\n") + os.utime(source, (mtime, mtime)) + + # Generate the .pyc/o file; if it couldn't be created + # for some reason, skip the test. + m = __import__(module) + if not os.path.exists(compiled): + unlink(source) + self.skipTest("Couldn't create .pyc/.pyo file.") + + # Actual modification time of .py file. + mtime1 = int(os.stat(source).st_mtime) & 0xffffffff + + # mtime that was encoded in the .pyc file. + with open(compiled, 'rb') as f: + mtime2 = struct.unpack('= previous_groups[0]), + self.assertGreaterEqual(int(groups[0]), int(previous_groups[0]), "Non-monotonic seconds: '%s' before '%s'" % (previous_groups[0], groups[0])) - self.assertTrue(int(groups[1] >= previous_groups[1]) or - groups[0] != groups[1], - "Non-monotonic milliseconds: '%s' before '%s'" % - (previous_groups[1], groups[1])) + if int(groups[0]) == int(previous_groups[0]): + self.assertGreaterEqual(int(groups[1]), int(previous_groups[1]), + "Non-monotonic milliseconds: '%s' before '%s'" % + (previous_groups[1], groups[1])) self.assertTrue(int(groups[2]) == pid, "Process ID mismatch: '%s' should be '%s'" % (groups[2], pid)) @@ -813,7 +844,49 @@ self._box._refresh() self.assertTrue(refreshed()) -class _TestMboxMMDF(TestMailbox): + +class _TestSingleFile(TestMailbox): + '''Common tests for single-file mailboxes''' + + def test_add_doesnt_rewrite(self): + # When only adding messages, flush() should not rewrite the + # mailbox file. See issue #9559. + + # Inode number changes if the contents are written to another + # file which is then renamed over the original file. So we + # must check that the inode number doesn't change. + inode_before = os.stat(self._path).st_ino + + self._box.add(self._template % 0) + self._box.flush() + + inode_after = os.stat(self._path).st_ino + self.assertEqual(inode_before, inode_after) + + # Make sure the message was really added + self._box.close() + self._box = self._factory(self._path) + self.assertEqual(len(self._box), 1) + + def test_permissions_after_flush(self): + # See issue #5346 + + # Make the mailbox world writable. It's unlikely that the new + # mailbox file would have these permissions after flush(), + # because umask usually prevents it. + mode = os.stat(self._path).st_mode | 0o666 + os.chmod(self._path, mode) + + self._box.add(self._template % 0) + i = self._box.add(self._template % 1) + # Need to remove one message to make flush() create a new file + self._box.remove(i) + self._box.flush() + + self.assertEqual(os.stat(self._path).st_mode, mode) + + +class _TestMboxMMDF(_TestSingleFile): def tearDown(self): self._box.close() @@ -823,14 +896,14 @@ def test_add_from_string(self): # Add a string starting with 'From ' to the mailbox - key = self._box.add('From foo at bar blah\nFrom: foo\n\n0') + key = self._box.add('From foo at bar blah\nFrom: foo\n\n0\n') self.assertEqual(self._box[key].get_from(), 'foo at bar blah') - self.assertEqual(self._box[key].get_payload(), '0') + self.assertEqual(self._box[key].get_payload(), '0\n') def test_add_mbox_or_mmdf_message(self): # Add an mboxMessage or MMDFMessage for class_ in (mailbox.mboxMessage, mailbox.MMDFMessage): - msg = class_('From foo at bar blah\nFrom: foo\n\n0') + msg = class_('From foo at bar blah\nFrom: foo\n\n0\n') key = self._box.add(msg) def test_open_close_open(self): @@ -914,7 +987,7 @@ self._box.close() -class TestMbox(_TestMboxMMDF): +class TestMbox(_TestMboxMMDF, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.mbox(path, factory) @@ -937,12 +1010,35 @@ perms = st.st_mode self.assertFalse((perms & 0111)) # Execute bits should all be off. -class TestMMDF(_TestMboxMMDF): + def test_terminating_newline(self): + message = email.message.Message() + message['From'] = 'john at example.com' + message.set_payload('No newline at the end') + i = self._box.add(message) + + # A newline should have been appended to the payload + message = self._box.get(i) + self.assertEqual(message.get_payload(), 'No newline at the end\n') + + def test_message_separator(self): + # Check there's always a single blank line after each message + self._box.add('From: foo\n\n0') # No newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + self._box.add('From: foo\n\n0\n') # Newline at the end + with open(self._path) as f: + data = f.read() + self.assertEqual(data[-3:], '0\n\n') + + +class TestMMDF(_TestMboxMMDF, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.MMDF(path, factory) -class TestMH(TestMailbox): +class TestMH(TestMailbox, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.MH(path, factory) @@ -1074,7 +1170,7 @@ return os.path.join(self._path, '.mh_sequences.lock') -class TestBabyl(TestMailbox): +class TestBabyl(_TestSingleFile, unittest.TestCase): _factory = lambda self, path, factory=None: mailbox.Babyl(path, factory) @@ -1103,7 +1199,7 @@ self.assertEqual(set(self._box.get_labels()), set(['blah'])) -class TestMessage(TestBase): +class TestMessage(TestBase, unittest.TestCase): _factory = mailbox.Message # Overridden by subclasses to reuse tests @@ -1174,7 +1270,7 @@ pass -class TestMaildirMessage(TestMessage): +class TestMaildirMessage(TestMessage, unittest.TestCase): _factory = mailbox.MaildirMessage @@ -1249,7 +1345,7 @@ self._check_sample(msg) -class _TestMboxMMDFMessage(TestMessage): +class _TestMboxMMDFMessage: _factory = mailbox._mboxMMDFMessage @@ -1296,12 +1392,12 @@ r"\d{2} \d{4}", msg.get_from())) -class TestMboxMessage(_TestMboxMMDFMessage): +class TestMboxMessage(_TestMboxMMDFMessage, TestMessage): _factory = mailbox.mboxMessage -class TestMHMessage(TestMessage): +class TestMHMessage(TestMessage, unittest.TestCase): _factory = mailbox.MHMessage @@ -1332,7 +1428,7 @@ self.assertEqual(msg.get_sequences(), ['foobar', 'replied']) -class TestBabylMessage(TestMessage): +class TestBabylMessage(TestMessage, unittest.TestCase): _factory = mailbox.BabylMessage @@ -1387,12 +1483,12 @@ self.assertEqual(visible[header], msg[header]) -class TestMMDFMessage(_TestMboxMMDFMessage): +class TestMMDFMessage(_TestMboxMMDFMessage, TestMessage): _factory = mailbox.MMDFMessage -class TestMessageConversion(TestBase): +class TestMessageConversion(TestBase, unittest.TestCase): def test_plain_to_x(self): # Convert Message to all formats @@ -1715,7 +1811,7 @@ proxy.close() -class TestProxyFile(TestProxyFileBase): +class TestProxyFile(TestProxyFileBase, unittest.TestCase): def setUp(self): self._path = test_support.TESTFN @@ -1764,7 +1860,7 @@ self._test_close(mailbox._ProxyFile(self._file)) -class TestPartialFile(TestProxyFileBase): +class TestPartialFile(TestProxyFileBase, unittest.TestCase): def setUp(self): self._path = test_support.TESTFN @@ -1831,6 +1927,10 @@ def setUp(self): # create a new maildir mailbox to work with: self._dir = test_support.TESTFN + if os.path.isdir(self._dir): + test_support.rmtree(self._dir) + if os.path.isfile(self._dir): + test_support.unlink(self._dir) os.mkdir(self._dir) os.mkdir(os.path.join(self._dir, "cur")) os.mkdir(os.path.join(self._dir, "tmp")) @@ -1840,10 +1940,10 @@ def tearDown(self): map(os.unlink, self._msgfiles) - os.rmdir(os.path.join(self._dir, "cur")) - os.rmdir(os.path.join(self._dir, "tmp")) - os.rmdir(os.path.join(self._dir, "new")) - os.rmdir(self._dir) + test_support.rmdir(os.path.join(self._dir, "cur")) + test_support.rmdir(os.path.join(self._dir, "tmp")) + test_support.rmdir(os.path.join(self._dir, "new")) + test_support.rmdir(self._dir) def createMessage(self, dir, mbox=False): t = int(time.time() % 1000000) @@ -1879,7 +1979,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1887,7 +1989,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1896,8 +2000,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 2) - self.assertIsNot(self.mbox.next(), None) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1906,11 +2014,13 @@ import email.parser fname = self.createMessage("cur", True) n = 0 - for msg in mailbox.PortableUnixMailbox(open(fname), + fid = open(fname) + for msg in mailbox.PortableUnixMailbox(fid, email.parser.Parser().parse): n += 1 self.assertEqual(msg["subject"], "Simple Test") self.assertEqual(len(str(msg)), len(FROM_)+len(DUMMY_MESSAGE)) + fid.close() self.assertEqual(n, 1) ## End: classes from the original module (for backward compatibility). diff --git a/lib-python/2.7/test/test_marshal.py b/lib-python/2.7/test/test_marshal.py --- a/lib-python/2.7/test/test_marshal.py +++ b/lib-python/2.7/test/test_marshal.py @@ -269,6 +269,53 @@ invalid_string = 'l\x02\x00\x00\x00\x00\x00\x00\x00' self.assertRaises(ValueError, marshal.loads, invalid_string) +LARGE_SIZE = 2**31 +character_size = 4 if sys.maxunicode > 0xFFFF else 2 +pointer_size = 8 if sys.maxsize > 0xFFFFFFFF else 4 + + at unittest.skipIf(LARGE_SIZE > sys.maxsize, "test cannot run on 32-bit systems") +class LargeValuesTestCase(unittest.TestCase): + def check_unmarshallable(self, data): + f = open(test_support.TESTFN, 'wb') + self.addCleanup(test_support.unlink, test_support.TESTFN) + with f: + self.assertRaises(ValueError, marshal.dump, data, f) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, memuse=1, dry_run=False) + def test_string(self, size): + self.check_unmarshallable('x' * size) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=character_size, dry_run=False) + def test_unicode(self, size): + self.check_unmarshallable(u'x' * size) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=pointer_size, dry_run=False) + def test_tuple(self, size): + self.check_unmarshallable((None,) * size) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=pointer_size, dry_run=False) + def test_list(self, size): + self.check_unmarshallable([None] * size) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=pointer_size*12 + sys.getsizeof(LARGE_SIZE-1), + dry_run=False) + def test_set(self, size): + self.check_unmarshallable(set(range(size))) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, + memuse=pointer_size*12 + sys.getsizeof(LARGE_SIZE-1), + dry_run=False) + def test_frozenset(self, size): + self.check_unmarshallable(frozenset(range(size))) + + @test_support.precisionbigmemtest(size=LARGE_SIZE, memuse=1, dry_run=False) + def test_bytearray(self, size): + self.check_unmarshallable(bytearray(size)) + def test_main(): test_support.run_unittest(IntTestCase, @@ -277,7 +324,9 @@ CodeTestCase, ContainerTestCase, ExceptionTestCase, - BugsTestCase) + BugsTestCase, + LargeValuesTestCase, + ) if __name__ == "__main__": test_main() diff --git a/lib-python/2.7/test/test_memoryio.py b/lib-python/2.7/test/test_memoryio.py --- a/lib-python/2.7/test/test_memoryio.py +++ b/lib-python/2.7/test/test_memoryio.py @@ -328,9 +328,9 @@ self.assertEqual(memio.isatty(), False) self.assertEqual(memio.closed, False) memio.close() - self.assertEqual(memio.writable(), True) - self.assertEqual(memio.readable(), True) - self.assertEqual(memio.seekable(), True) + self.assertRaises(ValueError, memio.writable) + self.assertRaises(ValueError, memio.readable) + self.assertRaises(ValueError, memio.seekable) self.assertRaises(ValueError, memio.isatty) self.assertEqual(memio.closed, True) @@ -638,6 +638,16 @@ memio.close() self.assertRaises(ValueError, memio.__setstate__, (b"closed", 0, None)) + check_sizeof = support.check_sizeof + + @support.cpython_only + def test_sizeof(self): + basesize = support.calcobjsize(b'P2PP2P') + check = self.check_sizeof + self.assertEqual(object.__sizeof__(io.BytesIO()), basesize) + check(io.BytesIO(), basesize ) + check(io.BytesIO(b'a'), basesize + 1 + 1 ) + check(io.BytesIO(b'a' * 1000), basesize + 1000 + 1 ) class CStringIOTest(PyStringIOTest): ioclass = io.StringIO diff --git a/lib-python/2.7/test/test_minidom.py b/lib-python/2.7/test/test_minidom.py --- a/lib-python/2.7/test/test_minidom.py +++ b/lib-python/2.7/test/test_minidom.py @@ -1060,7 +1060,7 @@ '\xa4', "testEncodings - encoding EURO SIGN") - # Verify that character decoding errors throw exceptions instead + # Verify that character decoding errors raise exceptions instead # of crashing self.assertRaises(UnicodeDecodeError, parseString, 'Comment \xe7a va ? Tr\xe8s bien ?') diff --git a/lib-python/2.7/test/test_mmap.py b/lib-python/2.7/test/test_mmap.py --- a/lib-python/2.7/test/test_mmap.py +++ b/lib-python/2.7/test/test_mmap.py @@ -466,6 +466,19 @@ f.flush () return mmap.mmap (f.fileno(), 0) + def test_empty_file (self): + f = open (TESTFN, 'w+b') + f.close() + with open(TESTFN, "rb") as f : + try: + m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) + m.close() + self.fail("should not have been able to mmap empty file") + except ValueError as e: + self.assertEqual(e.message, "cannot mmap an empty file") + except: + self.fail("unexpected exception: " + str(e)) + def test_offset (self): f = open (TESTFN, 'w+b') @@ -669,6 +682,13 @@ def test_large_filesize(self): with self._make_test_file(0x17FFFFFFF, b" ") as f: + if sys.maxsize < 0x180000000: + # On 32 bit platforms the file is larger than sys.maxsize so + # mapping the whole file should fail -- Issue #16743 + with self.assertRaises(OverflowError): + mmap.mmap(f.fileno(), 0x180000000, access=mmap.ACCESS_READ) + with self.assertRaises(ValueError): + mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) m = mmap.mmap(f.fileno(), 0x10000, access=mmap.ACCESS_READ) try: self.assertEqual(m.size(), 0x180000000) diff --git a/lib-python/2.7/test/test_multiprocessing.py b/lib-python/2.7/test/test_multiprocessing.py --- a/lib-python/2.7/test/test_multiprocessing.py +++ b/lib-python/2.7/test/test_multiprocessing.py @@ -16,6 +16,7 @@ import random import logging import errno +import test.script_helper from test import test_support from StringIO import StringIO _multiprocessing = test_support.import_module('_multiprocessing') @@ -325,6 +326,36 @@ ] self.assertEqual(result, expected) + @classmethod + def _test_sys_exit(cls, reason, testfn): + sys.stderr = open(testfn, 'w') + sys.exit(reason) + + def test_sys_exit(self): + # See Issue 13854 + if self.TYPE == 'threads': + return + + testfn = test_support.TESTFN + self.addCleanup(test_support.unlink, testfn) + + for reason, code in (([1, 2, 3], 1), ('ignore this', 0)): + p = self.Process(target=self._test_sys_exit, args=(reason, testfn)) + p.daemon = True + p.start() + p.join(5) + self.assertEqual(p.exitcode, code) + + with open(testfn, 'r') as f: + self.assertEqual(f.read().rstrip(), str(reason)) + + for reason in (True, False, 8): + p = self.Process(target=sys.exit, args=(reason,)) + p.daemon = True + p.start() + p.join(5) + self.assertEqual(p.exitcode, reason) + # # # @@ -1152,6 +1183,36 @@ join() self.assertTrue(join.elapsed < 0.2) + def test_empty_iterable(self): + # See Issue 12157 + p = self.Pool(1) + + self.assertEqual(p.map(sqr, []), []) + self.assertEqual(list(p.imap(sqr, [])), []) + self.assertEqual(list(p.imap_unordered(sqr, [])), []) + self.assertEqual(p.map_async(sqr, []).get(), []) + + p.close() + p.join() + +def unpickleable_result(): + return lambda: 42 + +class _TestPoolWorkerErrors(BaseTestCase): + ALLOWED_TYPES = ('processes', ) + + def test_unpickleable_result(self): + from multiprocessing.pool import MaybeEncodingError + p = multiprocessing.Pool(2) + + # Make sure we don't lose pool processes because of encoding errors. + for iteration in range(20): + res = p.apply_async(unpickleable_result) + self.assertRaises(MaybeEncodingError, res.get) + + p.close() + p.join() + class _TestPoolWorkerLifetime(BaseTestCase): ALLOWED_TYPES = ('processes', ) @@ -1452,6 +1513,7 @@ self.assertTimingAlmostEqual(poll.elapsed, TIMEOUT1) conn.send(None) + time.sleep(.1) self.assertEqual(poll(TIMEOUT1), True) self.assertTimingAlmostEqual(poll.elapsed, 0) @@ -1651,6 +1713,23 @@ self.assertEqual(conn.recv(), 'hello') p.join() l.close() + + def test_issue14725(self): + l = self.connection.Listener() + p = self.Process(target=self._test, args=(l.address,)) + p.daemon = True + p.start() + time.sleep(1) + # On Windows the client process should by now have connected, + # written data and closed the pipe handle by now. This causes + # ConnectNamdedPipe() to fail with ERROR_NO_DATA. See Issue + # 14725. + conn = l.accept() + self.assertEqual(conn.recv(), 'hello') + conn.close() + p.join() + l.close() + # # Test of sending connection and socket objects between processes # @@ -2026,6 +2105,38 @@ # assert self.__handled # +# Check that Process.join() retries if os.waitpid() fails with EINTR +# + +class _TestPollEintr(BaseTestCase): + + ALLOWED_TYPES = ('processes',) + + @classmethod + def _killer(cls, pid): + time.sleep(0.5) + os.kill(pid, signal.SIGUSR1) + + @unittest.skipUnless(hasattr(signal, 'SIGUSR1'), 'requires SIGUSR1') + def test_poll_eintr(self): + got_signal = [False] + def record(*args): + got_signal[0] = True + pid = os.getpid() + oldhandler = signal.signal(signal.SIGUSR1, record) + try: + killer = self.Process(target=self._killer, args=(pid,)) + killer.start() + p = self.Process(target=time.sleep, args=(1,)) + p.start() + p.join() + self.assertTrue(got_signal[0]) + self.assertEqual(p.exitcode, 0) + killer.join() + finally: + signal.signal(signal.SIGUSR1, oldhandler) + +# # Test to verify handle verification, see issue 3321 # @@ -2078,7 +2189,7 @@ 'Queue', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event', 'Value', 'Array', 'RawValue', 'RawArray', 'current_process', 'active_children', 'Pipe', - 'connection', 'JoinableQueue' + 'connection', 'JoinableQueue', 'Pool' ))) testcases_processes = create_test_cases(ProcessesMixin, type='processes') @@ -2092,7 +2203,7 @@ locals().update(get_attributes(manager, ( 'Queue', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event', 'Value', 'Array', 'list', 'dict', - 'Namespace', 'JoinableQueue' + 'Namespace', 'JoinableQueue', 'Pool' ))) testcases_manager = create_test_cases(ManagerMixin, type='manager') @@ -2106,7 +2217,7 @@ 'Queue', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Condition', 'Event', 'Value', 'Array', 'current_process', 'active_children', 'Pipe', 'connection', 'dict', 'list', - 'Namespace', 'JoinableQueue' + 'Namespace', 'JoinableQueue', 'Pool' ))) testcases_threads = create_test_cases(ThreadsMixin, type='threads') @@ -2238,8 +2349,62 @@ flike.flush() assert sio.getvalue() == 'foo' +# +# Test interaction with socket timeouts - see Issue #6056 +# + +class TestTimeouts(unittest.TestCase): + @classmethod + def _test_timeout(cls, child, address): + time.sleep(1) + child.send(123) + child.close() + conn = multiprocessing.connection.Client(address) + conn.send(456) + conn.close() + + def test_timeout(self): + old_timeout = socket.getdefaulttimeout() + try: + socket.setdefaulttimeout(0.1) + parent, child = multiprocessing.Pipe(duplex=True) + l = multiprocessing.connection.Listener(family='AF_INET') + p = multiprocessing.Process(target=self._test_timeout, + args=(child, l.address)) + p.start() + child.close() + self.assertEqual(parent.recv(), 123) + parent.close() + conn = l.accept() + self.assertEqual(conn.recv(), 456) + conn.close() + l.close() + p.join(10) + finally: + socket.setdefaulttimeout(old_timeout) + +# +# Test what happens with no "if __name__ == '__main__'" +# + +class TestNoForkBomb(unittest.TestCase): + def test_noforkbomb(self): + name = os.path.join(os.path.dirname(__file__), 'mp_fork_bomb.py') + if WIN32: + rc, out, err = test.script_helper.assert_python_failure(name) + self.assertEqual('', out.decode('ascii')) + self.assertIn('RuntimeError', err.decode('ascii')) + else: + rc, out, err = test.script_helper.assert_python_ok(name) + self.assertEqual('123', out.decode('ascii').rstrip()) + self.assertEqual('', err.decode('ascii')) + +# +# +# + testcases_other = [OtherTest, TestInvalidHandle, TestInitializers, - TestStdinBadfiledescriptor] + TestStdinBadfiledescriptor, TestTimeouts, TestNoForkBomb] # # diff --git a/lib-python/2.7/test/test_mutex.py b/lib-python/2.7/test/test_mutex.py --- a/lib-python/2.7/test/test_mutex.py +++ b/lib-python/2.7/test/test_mutex.py @@ -14,7 +14,7 @@ m.lock(called_by_mutex2, "eggs") def called_by_mutex2(some_data): - self.assertEquals(some_data, "eggs") + self.assertEqual(some_data, "eggs") self.assertTrue(m.test(), "mutex not held") self.assertTrue(ready_for_2, "called_by_mutex2 called too soon") diff --git a/lib-python/2.7/test/test_old_mailbox.py b/lib-python/2.7/test/test_old_mailbox.py --- a/lib-python/2.7/test/test_old_mailbox.py +++ b/lib-python/2.7/test/test_old_mailbox.py @@ -73,7 +73,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 1) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) @@ -81,7 +83,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 1) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) @@ -90,8 +94,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 2) - self.assertTrue(self.mbox.next() is not None) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) diff --git a/lib-python/2.7/test/test_optparse.py b/lib-python/2.7/test/test_optparse.py --- a/lib-python/2.7/test/test_optparse.py +++ b/lib-python/2.7/test/test_optparse.py @@ -769,6 +769,13 @@ self.assertParseFail(["-test"], "no such option: -e") + def test_add_option_accepts_unicode(self): + self.parser.add_option(u"-u", u"--unicode", action="store_true") + self.assertParseOK(["-u"], + {'a': None, 'boo': None, 'foo': None, 'unicode': True}, + []) + + class TestBool(BaseTest): def setUp(self): options = [make_option("-v", diff --git a/lib-python/2.7/test/test_os.py b/lib-python/2.7/test/test_os.py --- a/lib-python/2.7/test/test_os.py +++ b/lib-python/2.7/test/test_os.py @@ -214,33 +214,33 @@ try: result[200] - self.fail("No exception thrown") + self.fail("No exception raised") except IndexError: pass # Make sure that assignment fails try: result.st_mode = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except (AttributeError, TypeError): pass try: result.st_rdev = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except (AttributeError, TypeError): pass try: result.parrot = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except AttributeError: pass # Use the stat_result constructor with a too-short tuple. try: result2 = os.stat_result((10,)) - self.fail("No exception thrown") + self.fail("No exception raised") except TypeError: pass @@ -274,20 +274,20 @@ # Make sure that assignment really fails try: result.f_bfree = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except TypeError: pass try: result.parrot = 1 - self.fail("No exception thrown") + self.fail("No exception raised") except AttributeError: pass # Use the constructor with a too-short tuple. try: result2 = os.statvfs_result((10,)) - self.fail("No exception thrown") + self.fail("No exception raised") except TypeError: pass diff --git a/lib-python/2.7/test/test_parser.py b/lib-python/2.7/test/test_parser.py --- a/lib-python/2.7/test/test_parser.py +++ b/lib-python/2.7/test/test_parser.py @@ -1,7 +1,8 @@ import parser import unittest import sys -from test import test_support +import struct +from test import test_support as support # # First, we test that we can generate trees from valid source fragments, @@ -566,6 +567,17 @@ st = parser.suite('a = u"\u1"') self.assertRaises(SyntaxError, parser.compilest, st) + def test_issue_9011(self): + # Issue 9011: compilation of an unary minus expression changed + # the meaning of the ST, so that a second compilation produced + # incorrect results. + st = parser.expr('-3') + code1 = parser.compilest(st) + self.assertEqual(eval(code1), -3) + code2 = parser.compilest(st) + self.assertEqual(eval(code2), -3) + + class ParserStackLimitTestCase(unittest.TestCase): """try to push the parser to/over it's limits. see http://bugs.python.org/issue1881 for a discussion @@ -583,12 +595,57 @@ print >>sys.stderr, "Expecting 's_push: parser stack overflow' in next line" self.assertRaises(MemoryError, parser.expr, e) +class STObjectTestCase(unittest.TestCase): + """Test operations on ST objects themselves""" + + check_sizeof = support.check_sizeof + + @support.cpython_only + def test_sizeof(self): + def XXXROUNDUP(n): + if n <= 1: + return n + if n <= 128: + return (n + 3) & ~3 + return 1 << (n - 1).bit_length() + + basesize = support.calcobjsize('Pii') + nodesize = struct.calcsize('hP3iP0h') + def sizeofchildren(node): + if node is None: + return 0 + res = 0 + hasstr = len(node) > 1 and isinstance(node[-1], str) + if hasstr: + res += len(node[-1]) + 1 + children = node[1:-1] if hasstr else node[1:] + if children: + res += XXXROUNDUP(len(children)) * nodesize + for child in children: + res += sizeofchildren(child) + return res + + def check_st_sizeof(st): + self.check_sizeof(st, basesize + nodesize + + sizeofchildren(st.totuple())) + + check_st_sizeof(parser.expr('2 + 3')) + check_st_sizeof(parser.expr('2 + 3 + 4')) + check_st_sizeof(parser.suite('x = 2 + 3')) + check_st_sizeof(parser.suite('')) + check_st_sizeof(parser.suite('# -*- coding: utf-8 -*-')) + check_st_sizeof(parser.expr('[' + '2,' * 1000 + ']')) + + + # XXX tests for pickling and unpickling of ST objects should go here + def test_main(): - test_support.run_unittest( + support.run_unittest( RoundtripLegalSyntaxTestCase, IllegalSyntaxTestCase, CompileTestCase, ParserStackLimitTestCase, + STObjectTestCase, ) diff --git a/lib-python/2.7/test/test_pdb.py b/lib-python/2.7/test/test_pdb.py --- a/lib-python/2.7/test/test_pdb.py +++ b/lib-python/2.7/test/test_pdb.py @@ -6,12 +6,69 @@ import os import unittest import subprocess +import textwrap from test import test_support # This little helper class is essential for testing pdb under doctest. from test_doctest import _FakeInput +class PdbTestCase(unittest.TestCase): + + def run_pdb(self, script, commands): + """Run 'script' lines with pdb and the pdb 'commands'.""" + filename = 'main.py' + with open(filename, 'w') as f: + f.write(textwrap.dedent(script)) + self.addCleanup(test_support.unlink, filename) + cmd = [sys.executable, '-m', 'pdb', filename] + stdout = stderr = None + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + stdout, stderr = proc.communicate(commands) + proc.stdout.close() + proc.stdin.close() + return stdout, stderr + + def test_issue13183(self): + script = """ + from bar import bar + + def foo(): + bar() + + def nope(): + pass + + def foobar(): + foo() + nope() + + foobar() + """ + commands = """ + from bar import bar + break bar + continue + step + step + quit + """ + bar = """ + def bar(): + pass + """ + with open('bar.py', 'w') as f: + f.write(textwrap.dedent(bar)) + self.addCleanup(test_support.unlink, 'bar.py') + stdout, stderr = self.run_pdb(script, commands) + self.assertTrue( + any('main.py(5)foo()->None' in l for l in stdout.splitlines()), + 'Fail to step into the caller after a return') + + class PdbTestInput(object): """Context manager that makes testing Pdb in doctests easier.""" @@ -309,7 +366,9 @@ def test_main(): from test import test_pdb test_support.run_doctest(test_pdb, verbosity=True) - test_support.run_unittest(ModuleInitTester) + test_support.run_unittest( + PdbTestCase, + ModuleInitTester) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_peepholer.py b/lib-python/2.7/test/test_peepholer.py --- a/lib-python/2.7/test/test_peepholer.py +++ b/lib-python/2.7/test/test_peepholer.py @@ -138,21 +138,22 @@ self.assertIn('(1000)', asm) def test_binary_subscr_on_unicode(self): - # valid code get optimized + # unicode strings don't get optimized asm = dis_single('u"foo"[0]') - self.assertIn("(u'f')", asm) - self.assertNotIn('BINARY_SUBSCR', asm) + self.assertNotIn("(u'f')", asm) + self.assertIn('BINARY_SUBSCR', asm) asm = dis_single('u"\u0061\uffff"[1]') - self.assertIn("(u'\\uffff')", asm) - self.assertNotIn('BINARY_SUBSCR', asm) + self.assertNotIn("(u'\\uffff')", asm) + self.assertIn('BINARY_SUBSCR', asm) - # invalid code doesn't get optimized # out of range asm = dis_single('u"fuu"[10]') self.assertIn('BINARY_SUBSCR', asm) # non-BMP char (see #5057) asm = dis_single('u"\U00012345"[0]') self.assertIn('BINARY_SUBSCR', asm) + asm = dis_single('u"\U00012345abcdef"[3]') + self.assertIn('BINARY_SUBSCR', asm) def test_folding_of_unaryops_on_constants(self): diff --git a/lib-python/2.7/test/test_pickle.py b/lib-python/2.7/test/test_pickle.py --- a/lib-python/2.7/test/test_pickle.py +++ b/lib-python/2.7/test/test_pickle.py @@ -3,10 +3,11 @@ from test import test_support -from test.pickletester import AbstractPickleTests -from test.pickletester import AbstractPickleModuleTests -from test.pickletester import AbstractPersistentPicklerTests -from test.pickletester import AbstractPicklerUnpicklerObjectTests +from test.pickletester import (AbstractPickleTests, + AbstractPickleModuleTests, + AbstractPersistentPicklerTests, + AbstractPicklerUnpicklerObjectTests, + BigmemPickleTests) class PickleTests(AbstractPickleTests, AbstractPickleModuleTests): @@ -66,6 +67,16 @@ pickler_class = pickle.Pickler unpickler_class = pickle.Unpickler +class PickleBigmemPickleTests(BigmemPickleTests): + + def dumps(self, arg, proto=0, fast=0): + # Ignore fast + return pickle.dumps(arg, proto) + + def loads(self, buf): + # Ignore fast + return pickle.loads(buf) + def test_main(): test_support.run_unittest( @@ -73,6 +84,7 @@ PicklerTests, PersPicklerTests, PicklerUnpicklerObjectTests, + PickleBigmemPickleTests, ) test_support.run_doctest(pickle) diff --git a/lib-python/2.7/test/test_poll.py b/lib-python/2.7/test/test_poll.py --- a/lib-python/2.7/test/test_poll.py +++ b/lib-python/2.7/test/test_poll.py @@ -1,6 +1,7 @@ # Test case for the os.poll() function import os, select, random, unittest +import _testcapi from test.test_support import TESTFN, run_unittest try: @@ -150,6 +151,15 @@ if x != 5: self.fail('Overflow must have occurred') + pollster = select.poll() + # Issue 15989 + self.assertRaises(OverflowError, pollster.register, 0, + _testcapi.SHRT_MAX + 1) + self.assertRaises(OverflowError, pollster.register, 0, + _testcapi.USHRT_MAX + 1) + self.assertRaises(OverflowError, pollster.poll, _testcapi.INT_MAX + 1) + self.assertRaises(OverflowError, pollster.poll, _testcapi.UINT_MAX + 1) + def test_main(): run_unittest(PollTests) diff --git a/lib-python/2.7/test/test_posix.py b/lib-python/2.7/test/test_posix.py --- a/lib-python/2.7/test/test_posix.py +++ b/lib-python/2.7/test/test_posix.py @@ -9,6 +9,7 @@ import sys import time import os +import platform import pwd import shutil import stat @@ -107,7 +108,11 @@ # If a non-privileged user invokes it, it should fail with OSError # EPERM. if os.getuid() != 0: - name = pwd.getpwuid(posix.getuid()).pw_name + try: + name = pwd.getpwuid(posix.getuid()).pw_name + except KeyError: + # the current UID may not have a pwd entry + raise unittest.SkipTest("need a pwd entry") try: posix.initgroups(name, 13) except OSError as e: @@ -217,26 +222,64 @@ if hasattr(posix, 'stat'): self.assertTrue(posix.stat(test_support.TESTFN)) - def _test_all_chown_common(self, chown_func, first_param): + def _test_all_chown_common(self, chown_func, first_param, stat_func): """Common code for chown, fchown and lchown tests.""" - if os.getuid() == 0: - try: - # Many linux distros have a nfsnobody user as MAX_UID-2 - # that makes a good test case for signedness issues. - # http://bugs.python.org/issue1747858 - # This part of the test only runs when run as root. - # Only scary people run their tests as root. - ent = pwd.getpwnam('nfsnobody') - chown_func(first_param, ent.pw_uid, ent.pw_gid) - except KeyError: - pass + def check_stat(uid, gid): + if stat_func is not None: + stat = stat_func(first_param) + self.assertEqual(stat.st_uid, uid) + self.assertEqual(stat.st_gid, gid) + uid = os.getuid() + gid = os.getgid() + # test a successful chown call + chown_func(first_param, uid, gid) + check_stat(uid, gid) + chown_func(first_param, -1, gid) + check_stat(uid, gid) + chown_func(first_param, uid, -1) + check_stat(uid, gid) + + if uid == 0: + # Try an amusingly large uid/gid to make sure we handle + # large unsigned values. (chown lets you use any + # uid/gid you like, even if they aren't defined.) + # + # This problem keeps coming up: + # http://bugs.python.org/issue1747858 + # http://bugs.python.org/issue4591 + # http://bugs.python.org/issue15301 + # Hopefully the fix in 4591 fixes it for good! + # + # This part of the test only runs when run as root. + # Only scary people run their tests as root. + + big_value = 2**31 + chown_func(first_param, big_value, big_value) + check_stat(big_value, big_value) + chown_func(first_param, -1, -1) + check_stat(big_value, big_value) + chown_func(first_param, uid, gid) + check_stat(uid, gid) + elif platform.system() in ('HP-UX', 'SunOS'): + # HP-UX and Solaris can allow a non-root user to chown() to root + # (issue #5113) + raise unittest.SkipTest("Skipping because of non-standard chown() " + "behavior") else: # non-root cannot chown to root, raises OSError - self.assertRaises(OSError, chown_func, - first_param, 0, 0) - - # test a successful chown call - chown_func(first_param, os.getuid(), os.getgid()) + self.assertRaises(OSError, chown_func, first_param, 0, 0) + check_stat(uid, gid) + self.assertRaises(OSError, chown_func, first_param, 0, -1) + check_stat(uid, gid) + if 0 not in os.getgroups(): + self.assertRaises(OSError, chown_func, first_param, -1, 0) + check_stat(uid, gid) + # test illegal types + for t in str, float: + self.assertRaises(TypeError, chown_func, first_param, t(uid), gid) + check_stat(uid, gid) + self.assertRaises(TypeError, chown_func, first_param, uid, t(gid)) + check_stat(uid, gid) @unittest.skipUnless(hasattr(posix, 'chown'), "test needs os.chown()") def test_chown(self): @@ -246,7 +289,8 @@ # re-create the file open(test_support.TESTFN, 'w').close() - self._test_all_chown_common(posix.chown, test_support.TESTFN) + self._test_all_chown_common(posix.chown, test_support.TESTFN, + getattr(posix, 'stat', None)) @unittest.skipUnless(hasattr(posix, 'fchown'), "test needs os.fchown()") def test_fchown(self): @@ -256,7 +300,8 @@ test_file = open(test_support.TESTFN, 'w') try: fd = test_file.fileno() - self._test_all_chown_common(posix.fchown, fd) + self._test_all_chown_common(posix.fchown, fd, + getattr(posix, 'fstat', None)) finally: test_file.close() @@ -265,7 +310,8 @@ os.unlink(test_support.TESTFN) # create a symlink os.symlink(_DUMMY_SYMLINK, test_support.TESTFN) - self._test_all_chown_common(posix.lchown, test_support.TESTFN) + self._test_all_chown_common(posix.lchown, test_support.TESTFN, + getattr(posix, 'lstat', None)) def test_chdir(self): if hasattr(posix, 'chdir'): @@ -324,7 +370,16 @@ def _test_chflags_regular_file(self, chflags_func, target_file): st = os.stat(target_file) self.assertTrue(hasattr(st, 'st_flags')) - chflags_func(target_file, st.st_flags | stat.UF_IMMUTABLE) + + # ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE. + try: + chflags_func(target_file, st.st_flags | stat.UF_IMMUTABLE) + except OSError as err: + if err.errno != errno.EOPNOTSUPP: + raise + msg = 'chflag UF_IMMUTABLE not supported by underlying fs' + self.skipTest(msg) + try: new_st = os.stat(target_file) self.assertEqual(st.st_flags | stat.UF_IMMUTABLE, new_st.st_flags) @@ -353,8 +408,16 @@ self.teardown_files.append(_DUMMY_SYMLINK) dummy_symlink_st = os.lstat(_DUMMY_SYMLINK) - posix.lchflags(_DUMMY_SYMLINK, - dummy_symlink_st.st_flags | stat.UF_IMMUTABLE) + # ZFS returns EOPNOTSUPP when attempting to set flag UF_IMMUTABLE. + try: + posix.lchflags(_DUMMY_SYMLINK, + dummy_symlink_st.st_flags | stat.UF_IMMUTABLE) + except OSError as err: + if err.errno != errno.EOPNOTSUPP: + raise + msg = 'chflag UF_IMMUTABLE not supported by underlying fs' + self.skipTest(msg) + try: new_testfn_st = os.stat(test_support.TESTFN) new_dummy_symlink_st = os.lstat(_DUMMY_SYMLINK) @@ -395,8 +458,16 @@ _create_and_do_getcwd(dirname, current_path_length + len(dirname) + 1) except OSError as e: expected_errno = errno.ENAMETOOLONG - if 'sunos' in sys.platform or 'openbsd' in sys.platform: - expected_errno = errno.ERANGE # Issue 9185 + # The following platforms have quirky getcwd() + # behaviour -- see issue 9185 and 15765 for + # more information. + quirky_platform = ( + 'sunos' in sys.platform or + 'netbsd' in sys.platform or + 'openbsd' in sys.platform + ) + if quirky_platform: + expected_errno = errno.ERANGE self.assertEqual(e.errno, expected_errno) finally: os.chdir('..') @@ -412,10 +483,18 @@ def test_getgroups(self): with os.popen('id -G') as idg: groups = idg.read().strip() + ret = idg.close() - if not groups: + if ret != None or not groups: raise unittest.SkipTest("need working 'id -G'") + # Issues 16698: OS X ABIs prior to 10.6 have limits on getgroups() + if sys.platform == 'darwin': + import sysconfig + dt = sysconfig.get_config_var('MACOSX_DEPLOYMENT_TARGET') or '10.0' + if float(dt) < 10.6: + raise unittest.SkipTest("getgroups(2) is broken prior to 10.6") + # 'id -G' and 'os.getgroups()' should return the same # groups, ignoring order and duplicates. # #10822 - it is implementation defined whether posix.getgroups() diff --git a/lib-python/2.7/test/test_posixpath.py b/lib-python/2.7/test/test_posixpath.py --- a/lib-python/2.7/test/test_posixpath.py +++ b/lib-python/2.7/test/test_posixpath.py @@ -9,6 +9,16 @@ ABSTFN = abspath(test_support.TESTFN) +def skip_if_ABSTFN_contains_backslash(test): + """ + On Windows, posixpath.abspath still returns paths with backslashes + instead of posix forward slashes. If this is the case, several tests + fail, so skip them. + """ + found_backslash = '\\' in ABSTFN + msg = "ABSTFN is not a posix path - tests fail" + return [test, unittest.skip(msg)(test)][found_backslash] + def safe_rmdir(dirname): try: os.rmdir(dirname) @@ -110,8 +120,10 @@ ), True ) - # If we don't have links, assume that os.stat doesn't return resonable - # inode information and thus, that samefile() doesn't work + + # If we don't have links, assume that os.stat doesn't return + # reasonable inode information and thus, that samefile() doesn't + # work. if hasattr(os, "symlink"): os.symlink( test_support.TESTFN + "1", @@ -151,19 +163,19 @@ ), True ) - # If we don't have links, assume that os.stat() doesn't return resonable - # inode information and thus, that samefile() doesn't work + # If we don't have links, assume that os.stat() doesn't return + # reasonable inode information and thus, that samestat() doesn't + # work. if hasattr(os, "symlink"): - if hasattr(os, "symlink"): - os.symlink(test_support.TESTFN + "1", test_support.TESTFN + "2") - self.assertIs( - posixpath.samestat( - os.stat(test_support.TESTFN + "1"), - os.stat(test_support.TESTFN + "2") - ), - True - ) - os.remove(test_support.TESTFN + "2") + os.symlink(test_support.TESTFN + "1", test_support.TESTFN + "2") + self.assertIs( + posixpath.samestat( + os.stat(test_support.TESTFN + "1"), + os.stat(test_support.TESTFN + "2") + ), + True + ) + os.remove(test_support.TESTFN + "2") f = open(test_support.TESTFN + "2", "wb") f.write("bar") f.close() @@ -201,6 +213,7 @@ with test_support.EnvironmentVarGuard() as env: env['HOME'] = '/' self.assertEqual(posixpath.expanduser("~"), "/") + self.assertEqual(posixpath.expanduser("~/foo"), "/foo") def test_normpath(self): self.assertEqual(posixpath.normpath(""), ".") @@ -211,6 +224,18 @@ self.assertEqual(posixpath.normpath("///foo/.//bar//.//..//.//baz"), "/foo/baz") self.assertEqual(posixpath.normpath("///..//./foo/.//bar"), "/foo/bar") + @skip_if_ABSTFN_contains_backslash + def test_realpath_curdir(self): + self.assertEqual(realpath('.'), os.getcwd()) + self.assertEqual(realpath('./.'), os.getcwd()) + self.assertEqual(realpath('/'.join(['.'] * 100)), os.getcwd()) + + @skip_if_ABSTFN_contains_backslash + def test_realpath_pardir(self): + self.assertEqual(realpath('..'), dirname(os.getcwd())) + self.assertEqual(realpath('../..'), dirname(dirname(os.getcwd()))) + self.assertEqual(realpath('/'.join(['..'] * 100)), '/') + if hasattr(os, "symlink"): def test_realpath_basic(self): # Basic operation. @@ -233,6 +258,22 @@ self.assertEqual(realpath(ABSTFN+"1"), ABSTFN+"1") self.assertEqual(realpath(ABSTFN+"2"), ABSTFN+"2") + self.assertEqual(realpath(ABSTFN+"1/x"), ABSTFN+"1/x") + self.assertEqual(realpath(ABSTFN+"1/.."), dirname(ABSTFN)) + self.assertEqual(realpath(ABSTFN+"1/../x"), dirname(ABSTFN) + "/x") + os.symlink(ABSTFN+"x", ABSTFN+"y") + self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "y"), + ABSTFN + "y") + self.assertEqual(realpath(ABSTFN+"1/../" + basename(ABSTFN) + "1"), + ABSTFN + "1") + + os.symlink(basename(ABSTFN) + "a/b", ABSTFN+"a") + self.assertEqual(realpath(ABSTFN+"a"), ABSTFN+"a/b") + + os.symlink("../" + basename(dirname(ABSTFN)) + "/" + + basename(ABSTFN) + "c", ABSTFN+"c") + self.assertEqual(realpath(ABSTFN+"c"), ABSTFN+"c") + # Test using relative path as well. os.chdir(dirname(ABSTFN)) self.assertEqual(realpath(basename(ABSTFN)), ABSTFN) @@ -241,6 +282,40 @@ test_support.unlink(ABSTFN) test_support.unlink(ABSTFN+"1") test_support.unlink(ABSTFN+"2") + test_support.unlink(ABSTFN+"y") + test_support.unlink(ABSTFN+"c") + test_support.unlink(ABSTFN+"a") + + def test_realpath_repeated_indirect_symlinks(self): + # Issue #6975. + try: + os.mkdir(ABSTFN) + os.symlink('../' + basename(ABSTFN), ABSTFN + '/self') + os.symlink('self/self/self', ABSTFN + '/link') + self.assertEqual(realpath(ABSTFN + '/link'), ABSTFN) + finally: + test_support.unlink(ABSTFN + '/self') + test_support.unlink(ABSTFN + '/link') + safe_rmdir(ABSTFN) + + def test_realpath_deep_recursion(self): + depth = 10 + old_path = abspath('.') + try: + os.mkdir(ABSTFN) + for i in range(depth): + os.symlink('/'.join(['%d' % i] * 10), ABSTFN + '/%d' % (i + 1)) + os.symlink('.', ABSTFN + '/0') + self.assertEqual(realpath(ABSTFN + '/%d' % depth), ABSTFN) + + # Test using relative path as well. + os.chdir(ABSTFN) + self.assertEqual(realpath('%d' % depth), ABSTFN) + finally: + os.chdir(old_path) + for i in range(depth + 1): + test_support.unlink(ABSTFN + '/%d' % i) + safe_rmdir(ABSTFN) def test_realpath_resolve_parents(self): # We also need to resolve any symlinks in the parents of a relative diff --git a/lib-python/2.7/test/test_property.py b/lib-python/2.7/test/test_property.py --- a/lib-python/2.7/test/test_property.py +++ b/lib-python/2.7/test/test_property.py @@ -163,7 +163,7 @@ Foo.spam.__doc__, "spam wrapped in property subclass") - @unittest.skipIf(sys.flags.optimize <= 2, + @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_setter_copies_getter_docstring(self): class Foo(object): @@ -196,7 +196,7 @@ FooSub.spam.__doc__, "spam wrapped in property subclass") - @unittest.skipIf(sys.flags.optimize <= 2, + @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_new_getter_new_docstring(self): diff --git a/lib-python/2.7/test/test_pty.py b/lib-python/2.7/test/test_pty.py --- a/lib-python/2.7/test/test_pty.py +++ b/lib-python/2.7/test/test_pty.py @@ -152,7 +152,7 @@ # platform-dependent amount of data is written to its fd. On # Linux 2.6, it's 4000 bytes and the child won't block, but on OS # X even the small writes in the child above will block it. Also - # on Linux, the read() will throw an OSError (input/output error) + # on Linux, the read() will raise an OSError (input/output error) # when it tries to read past the end of the buffer but the child's # already exited, so catch and discard those exceptions. It's not # worth checking for EIO. diff --git a/lib-python/2.7/test/test_pwd.py b/lib-python/2.7/test/test_pwd.py --- a/lib-python/2.7/test/test_pwd.py +++ b/lib-python/2.7/test/test_pwd.py @@ -49,7 +49,9 @@ def test_errors(self): self.assertRaises(TypeError, pwd.getpwuid) + self.assertRaises(TypeError, pwd.getpwuid, 3.14) self.assertRaises(TypeError, pwd.getpwnam) + self.assertRaises(TypeError, pwd.getpwnam, 42) self.assertRaises(TypeError, pwd.getpwall, 42) # try to get some errors @@ -93,6 +95,13 @@ self.assertNotIn(fakeuid, byuids) self.assertRaises(KeyError, pwd.getpwuid, fakeuid) + # -1 shouldn't be a valid uid because it has a special meaning in many + # uid-related functions + self.assertRaises(KeyError, pwd.getpwuid, -1) + # should be out of uid_t range + self.assertRaises(KeyError, pwd.getpwuid, 2**128) + self.assertRaises(KeyError, pwd.getpwuid, -2**128) + def test_main(): test_support.run_unittest(PwdTest) diff --git a/lib-python/2.7/test/test_pyclbr.py b/lib-python/2.7/test/test_pyclbr.py --- a/lib-python/2.7/test/test_pyclbr.py +++ b/lib-python/2.7/test/test_pyclbr.py @@ -188,6 +188,11 @@ cm('email.parser') cm('test.test_pyclbr') + def test_issue_14798(self): + # test ImportError is raised when the first part of a dotted name is + # not a package + self.assertRaises(ImportError, pyclbr.readmodule_ex, 'asyncore.foo') + def test_main(): run_unittest(PyclbrTest) diff --git a/lib-python/2.7/test/test_pydoc.py b/lib-python/2.7/test/test_pydoc.py --- a/lib-python/2.7/test/test_pydoc.py +++ b/lib-python/2.7/test/test_pydoc.py @@ -16,6 +16,14 @@ from test import pydoc_mod +if test.test_support.HAVE_DOCSTRINGS: + expected_data_docstrings = ( + 'dictionary for instance variables (if defined)', + 'list of weak references to the object (if defined)', + ) +else: + expected_data_docstrings = ('', '') + expected_text_pattern = \ """ NAME @@ -40,11 +48,9 @@ class B(__builtin__.object) | Data descriptors defined here: |\x20\x20 - | __dict__ - | dictionary for instance variables (if defined) + | __dict__%s |\x20\x20 - | __weakref__ - | list of weak references to the object (if defined) + | __weakref__%s |\x20\x20 | ---------------------------------------------------------------------- | Data and other attributes defined here: @@ -75,6 +81,9 @@ Nobody """.strip() +expected_text_data_docstrings = tuple('\n | ' + s if s else '' + for s in expected_data_docstrings) + expected_html_pattern = \ """ @@ -121,10 +130,10 @@
     Data descriptors defined here:
__dict__
-
dictionary for instance variables (if defined)
+
%s
__weakref__
-
list of weak references to the object (if defined)
+
%s

Data and other attributes defined here:
@@ -168,6 +177,8 @@
Nobody
""".strip() +expected_html_data_docstrings = tuple(s.replace(' ', ' ') + for s in expected_data_docstrings) # output pattern for missing module missing_pattern = "no Python documentation found for '%s'" @@ -229,7 +240,9 @@ mod_url = nturl2path.pathname2url(mod_file) else: mod_url = mod_file - expected_html = expected_html_pattern % (mod_url, mod_file, doc_loc) + expected_html = expected_html_pattern % ( + (mod_url, mod_file, doc_loc) + + expected_html_data_docstrings) if result != expected_html: print_diffs(expected_html, result) self.fail("outputs are not equal, see diff above") @@ -238,8 +251,9 @@ "Docstrings are omitted with -O2 and above") def test_text_doc(self): result, doc_loc = get_pydoc_text(pydoc_mod) - expected_text = expected_text_pattern % \ - (inspect.getabsfile(pydoc_mod), doc_loc) + expected_text = expected_text_pattern % ( + (inspect.getabsfile(pydoc_mod), doc_loc) + + expected_text_data_docstrings) if result != expected_text: print_diffs(expected_text, result) self.fail("outputs are not equal, see diff above") @@ -249,6 +263,17 @@ result, doc_loc = get_pydoc_text(xml.etree) self.assertEqual(doc_loc, "", "MODULE DOCS incorrectly includes a link") + def test_non_str_name(self): + # issue14638 + # Treat illegal (non-str) name like no name + class A: + __name__ = 42 + class B: + pass + adoc = pydoc.render_doc(A()) + bdoc = pydoc.render_doc(B()) + self.assertEqual(adoc.replace("A", "B"), bdoc) + def test_not_here(self): missing_module = "test.i_am_not_here" result = run_pydoc(missing_module) diff --git a/lib-python/2.7/test/test_pyexpat.py b/lib-python/2.7/test/test_pyexpat.py --- a/lib-python/2.7/test/test_pyexpat.py +++ b/lib-python/2.7/test/test_pyexpat.py @@ -588,6 +588,58 @@ except expat.ExpatError as e: self.assertEqual(str(e), 'XML declaration not well-formed: line 1, column 14') +class ForeignDTDTests(unittest.TestCase): + """ + Tests for the UseForeignDTD method of expat parser objects. + """ + def test_use_foreign_dtd(self): + """ + If UseForeignDTD is passed True and a document without an external + entity reference is parsed, ExternalEntityRefHandler is first called + with None for the public and system ids. + """ + handler_call_args = [] + def resolve_entity(context, base, system_id, public_id): + handler_call_args.append((public_id, system_id)) + return 1 + + parser = expat.ParserCreate() + parser.UseForeignDTD(True) + parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_ALWAYS) + parser.ExternalEntityRefHandler = resolve_entity + parser.Parse("") + self.assertEqual(handler_call_args, [(None, None)]) + + # test UseForeignDTD() is equal to UseForeignDTD(True) + handler_call_args[:] = [] + + parser = expat.ParserCreate() + parser.UseForeignDTD() + parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_ALWAYS) + parser.ExternalEntityRefHandler = resolve_entity + parser.Parse("") + self.assertEqual(handler_call_args, [(None, None)]) + + def test_ignore_use_foreign_dtd(self): + """ + If UseForeignDTD is passed True and a document with an external + entity reference is parsed, ExternalEntityRefHandler is called with + the public and system ids from the document. + """ + handler_call_args = [] + def resolve_entity(context, base, system_id, public_id): + handler_call_args.append((public_id, system_id)) + return 1 + + parser = expat.ParserCreate() + parser.UseForeignDTD(True) + parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_ALWAYS) + parser.ExternalEntityRefHandler = resolve_entity + parser.Parse( + "") + self.assertEqual(handler_call_args, [("bar", "baz")]) + + def test_main(): run_unittest(SetAttributeTest, ParseTest, @@ -598,7 +650,8 @@ PositionTest, sf1296433Test, ChardataBufferTest, - MalformedInputText) + MalformedInputText, + ForeignDTDTests) if __name__ == "__main__": test_main() diff --git a/lib-python/2.7/test/test_random.py b/lib-python/2.7/test/test_random.py --- a/lib-python/2.7/test/test_random.py +++ b/lib-python/2.7/test/test_random.py @@ -57,6 +57,14 @@ self.assertRaises(TypeError, self.gen.jumpahead) # needs an arg self.assertRaises(TypeError, self.gen.jumpahead, 2, 3) # too many + def test_jumpahead_produces_valid_state(self): + # From http://bugs.python.org/issue14591. + self.gen.seed(199210368) + self.gen.jumpahead(13550674232554645900) + for i in range(500): + val = self.gen.random() + self.assertLess(val, 1.0) + def test_sample(self): # For the entire allowable range of 0 <= k <= N, validate that # the sample is of the correct length and contains only unique items @@ -486,6 +494,7 @@ g.random = x[:].pop; g.paretovariate(1.0) g.random = x[:].pop; g.expovariate(1.0) g.random = x[:].pop; g.weibullvariate(1.0, 1.0) + g.random = x[:].pop; g.vonmisesvariate(1.0, 1.0) g.random = x[:].pop; g.normalvariate(0.0, 1.0) g.random = x[:].pop; g.gauss(0.0, 1.0) g.random = x[:].pop; g.lognormvariate(0.0, 1.0) @@ -506,6 +515,7 @@ (g.uniform, (1.0,10.0), (10.0+1.0)/2, (10.0-1.0)**2/12), (g.triangular, (0.0, 1.0, 1.0/3.0), 4.0/9.0, 7.0/9.0/18.0), (g.expovariate, (1.5,), 1/1.5, 1/1.5**2), + (g.vonmisesvariate, (1.23, 0), pi, pi**2/3), (g.paretovariate, (5.0,), 5.0/(5.0-1), 5.0/((5.0-1)**2*(5.0-2))), (g.weibullvariate, (1.0, 3.0), gamma(1+1/3.0), @@ -522,8 +532,50 @@ s1 += e s2 += (e - mu) ** 2 N = len(y) - self.assertAlmostEqual(s1/N, mu, 2) - self.assertAlmostEqual(s2/(N-1), sigmasqrd, 2) + self.assertAlmostEqual(s1/N, mu, places=2, + msg='%s%r' % (variate.__name__, args)) + self.assertAlmostEqual(s2/(N-1), sigmasqrd, places=2, + msg='%s%r' % (variate.__name__, args)) + + def test_constant(self): + g = random.Random() + N = 100 + for variate, args, expected in [ + (g.uniform, (10.0, 10.0), 10.0), + (g.triangular, (10.0, 10.0), 10.0), + #(g.triangular, (10.0, 10.0, 10.0), 10.0), + (g.expovariate, (float('inf'),), 0.0), + (g.vonmisesvariate, (3.0, float('inf')), 3.0), + (g.gauss, (10.0, 0.0), 10.0), + (g.lognormvariate, (0.0, 0.0), 1.0), + (g.lognormvariate, (-float('inf'), 0.0), 0.0), + (g.normalvariate, (10.0, 0.0), 10.0), + (g.paretovariate, (float('inf'),), 1.0), + (g.weibullvariate, (10.0, float('inf')), 10.0), + (g.weibullvariate, (0.0, 10.0), 0.0), + ]: + for i in range(N): + self.assertEqual(variate(*args), expected) + + def test_von_mises_range(self): + # Issue 17149: von mises variates were not consistently in the + # range [0, 2*PI]. + g = random.Random() + N = 100 + for mu in 0.0, 0.1, 3.1, 6.2: + for kappa in 0.0, 2.3, 500.0: + for _ in range(N): + sample = g.vonmisesvariate(mu, kappa) + self.assertTrue( + 0 <= sample <= random.TWOPI, + msg=("vonmisesvariate({}, {}) produced a result {} out" + " of range [0, 2*pi]").format(mu, kappa, sample)) + + def test_von_mises_large_kappa(self): + # Issue #17141: vonmisesvariate() was hang for large kappas + random.vonmisesvariate(0, 1e15) + random.vonmisesvariate(0, 1e100) + class TestModule(unittest.TestCase): def testMagicConstants(self): diff --git a/lib-python/2.7/test/test_re.py b/lib-python/2.7/test/test_re.py --- a/lib-python/2.7/test/test_re.py +++ b/lib-python/2.7/test/test_re.py @@ -1,4 +1,5 @@ from test.test_support import verbose, run_unittest, import_module +from test.test_support import precisionbigmemtest, _2G, cpython_only import re from re import Scanner import sys @@ -6,6 +7,7 @@ import traceback from weakref import proxy + # Misc tests from Tim Peters' re.doc # WARNING: Don't change details in these tests if you don't know @@ -174,11 +176,31 @@ self.assertEqual(re.sub('x*', '-', 'abxd'), '-a-b-d-') self.assertEqual(re.sub('x+', '-', 'abxd'), 'ab-d') + def test_symbolic_groups(self): + re.compile('(?Px)(?P=a)(?(a)y)') + re.compile('(?Px)(?P=a1)(?(a1)y)') + self.assertRaises(re.error, re.compile, '(?P)(?P)') + self.assertRaises(re.error, re.compile, '(?Px)') + self.assertRaises(re.error, re.compile, '(?P=)') + self.assertRaises(re.error, re.compile, '(?P=1)') + self.assertRaises(re.error, re.compile, '(?P=a)') + self.assertRaises(re.error, re.compile, '(?P=a1)') + self.assertRaises(re.error, re.compile, '(?P=a.)') + self.assertRaises(re.error, re.compile, '(?P<)') + self.assertRaises(re.error, re.compile, '(?P<>)') + self.assertRaises(re.error, re.compile, '(?P<1>)') + self.assertRaises(re.error, re.compile, '(?P)') + self.assertRaises(re.error, re.compile, '(?())') + self.assertRaises(re.error, re.compile, '(?(a))') + self.assertRaises(re.error, re.compile, '(?(1a))') + self.assertRaises(re.error, re.compile, '(?(a.))') + def test_symbolic_refs(self): self.assertRaises(re.error, re.sub, '(?Px)', '\gx)', '\g<', 'xx') self.assertRaises(re.error, re.sub, '(?Px)', '\g', 'xx') self.assertRaises(re.error, re.sub, '(?Px)', '\g', 'xx') + self.assertRaises(re.error, re.sub, '(?Px)', '\g<>', 'xx') self.assertRaises(re.error, re.sub, '(?Px)', '\g<1a1>', 'xx') self.assertRaises(IndexError, re.sub, '(?Px)', '\g', 'xx') self.assertRaises(re.error, re.sub, '(?Px)|(?Py)', '\g', 'xx') @@ -405,6 +427,12 @@ self.assertEqual(re.match(u"([\u2222\u2223])", u"\u2222", re.UNICODE).group(1), u"\u2222") + def test_big_codesize(self): + # Issue #1160 + r = re.compile('|'.join(('%d'%x for x in range(10000)))) + self.assertIsNotNone(r.match('1000')) + self.assertIsNotNone(r.match('9999')) + def test_anyall(self): self.assertEqual(re.match("a.b", "a\nb", re.DOTALL).group(0), "a\nb") @@ -600,6 +628,15 @@ self.assertEqual(re.match('(x)*y', 50000*'x'+'y').group(1), 'x') self.assertEqual(re.match('(x)*?y', 50000*'x'+'y').group(1), 'x') + def test_unlimited_zero_width_repeat(self): + # Issue #9669 + self.assertIsNone(re.match(r'(?:a?)*y', 'z')) + self.assertIsNone(re.match(r'(?:a?)+y', 'z')) + self.assertIsNone(re.match(r'(?:a?){2,}y', 'z')) + self.assertIsNone(re.match(r'(?:a?)*?y', 'z')) + self.assertIsNone(re.match(r'(?:a?)+?y', 'z')) + self.assertIsNone(re.match(r'(?:a?){2,}?y', 'z')) + def test_scanner(self): def s_ident(scanner, token): return token def s_operator(scanner, token): return "op%s" % token @@ -793,6 +830,63 @@ # Test behaviour when not given a string or pattern as parameter self.assertRaises(TypeError, re.compile, 0) + def test_bug_13899(self): + # Issue #13899: re pattern r"[\A]" should work like "A" but matches + # nothing. Ditto B and Z. + self.assertEqual(re.findall(r'[\A\B\b\C\Z]', 'AB\bCZ'), + ['A', 'B', '\b', 'C', 'Z']) + + @precisionbigmemtest(size=_2G, memuse=1) + def test_large_search(self, size): + # Issue #10182: indices were 32-bit-truncated. + s = 'a' * size + m = re.search('$', s) + self.assertIsNotNone(m) + self.assertEqual(m.start(), size) + self.assertEqual(m.end(), size) + + # The huge memuse is because of re.sub() using a list and a join() + # to create the replacement result. + @precisionbigmemtest(size=_2G, memuse=16 + 2) + def test_large_subn(self, size): + # Issue #10182: indices were 32-bit-truncated. + s = 'a' * size + r, n = re.subn('', '', s) + self.assertEqual(r, s) + self.assertEqual(n, size + 1) + + + def test_repeat_minmax_overflow(self): + # Issue #13169 + string = "x" * 100000 + self.assertEqual(re.match(r".{65535}", string).span(), (0, 65535)) + self.assertEqual(re.match(r".{,65535}", string).span(), (0, 65535)) + self.assertEqual(re.match(r".{65535,}?", string).span(), (0, 65535)) + self.assertEqual(re.match(r".{65536}", string).span(), (0, 65536)) + self.assertEqual(re.match(r".{,65536}", string).span(), (0, 65536)) + self.assertEqual(re.match(r".{65536,}?", string).span(), (0, 65536)) + # 2**128 should be big enough to overflow both SRE_CODE and Py_ssize_t. + self.assertRaises(OverflowError, re.compile, r".{%d}" % 2**128) + self.assertRaises(OverflowError, re.compile, r".{,%d}" % 2**128) + self.assertRaises(OverflowError, re.compile, r".{%d,}?" % 2**128) + self.assertRaises(OverflowError, re.compile, r".{%d,%d}" % (2**129, 2**128)) + + @cpython_only + def test_repeat_minmax_overflow_maxrepeat(self): + try: + from _sre import MAXREPEAT + except ImportError: + self.skipTest('requires _sre.MAXREPEAT constant') + string = "x" * 100000 + self.assertIsNone(re.match(r".{%d}" % (MAXREPEAT - 1), string)) + self.assertEqual(re.match(r".{,%d}" % (MAXREPEAT - 1), string).span(), + (0, 100000)) + self.assertIsNone(re.match(r".{%d,}?" % (MAXREPEAT - 1), string)) + self.assertRaises(OverflowError, re.compile, r".{%d}" % MAXREPEAT) + self.assertRaises(OverflowError, re.compile, r".{,%d}" % MAXREPEAT) + self.assertRaises(OverflowError, re.compile, r".{%d,}?" % MAXREPEAT) + + def run_re_tests(): from test.re_tests import tests, SUCCEED, FAIL, SYNTAX_ERROR if verbose: diff --git a/lib-python/2.7/test/test_readline.py b/lib-python/2.7/test/test_readline.py --- a/lib-python/2.7/test/test_readline.py +++ b/lib-python/2.7/test/test_readline.py @@ -12,6 +12,10 @@ readline = import_module('readline') class TestHistoryManipulation (unittest.TestCase): + + @unittest.skipIf(not hasattr(readline, 'clear_history'), + "The history update test cannot be run because the " + "clear_history method is not available.") def testHistoryUpdates(self): readline.clear_history() diff --git a/lib-python/2.7/test/test_resource.py b/lib-python/2.7/test/test_resource.py --- a/lib-python/2.7/test/test_resource.py +++ b/lib-python/2.7/test/test_resource.py @@ -103,6 +103,23 @@ except (ValueError, AttributeError): pass + # Issue 6083: Reference counting bug + def test_setrusage_refcount(self): + try: + limits = resource.getrlimit(resource.RLIMIT_CPU) + except AttributeError: + pass + else: + class BadSequence: + def __len__(self): + return 2 + def __getitem__(self, key): + if key in (0, 1): + return len(tuple(range(1000000))) + raise IndexError + + resource.setrlimit(resource.RLIMIT_CPU, BadSequence()) + def test_main(verbose=None): test_support.run_unittest(ResourceTest) diff --git a/lib-python/2.7/test/test_sax.py b/lib-python/2.7/test/test_sax.py --- a/lib-python/2.7/test/test_sax.py +++ b/lib-python/2.7/test/test_sax.py @@ -14,12 +14,28 @@ from xml.sax.handler import feature_namespaces from xml.sax.xmlreader import InputSource, AttributesImpl, AttributesNSImpl from cStringIO import StringIO +import io +import os.path +import shutil +import test.test_support as support from test.test_support import findfile, run_unittest import unittest TEST_XMLFILE = findfile("test.xml", subdir="xmltestdata") TEST_XMLFILE_OUT = findfile("test.xml.out", subdir="xmltestdata") +supports_unicode_filenames = True +if not os.path.supports_unicode_filenames: + try: + support.TESTFN_UNICODE.encode(support.TESTFN_ENCODING) + except (AttributeError, UnicodeError, TypeError): + # Either the file system encoding is None, or the file name + # cannot be encoded in the file system encoding. + supports_unicode_filenames = False +requires_unicode_filenames = unittest.skipUnless( + supports_unicode_filenames, + 'Requires unicode filenames support') + ns_uri = "http://www.python.org/xml-ns/saxtest/" class XmlTestBase(unittest.TestCase): @@ -155,9 +171,9 @@ start = '\n' -class XmlgenTest(unittest.TestCase): +class XmlgenTest: def test_xmlgen_basic(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() gen.startElement("doc", {}) @@ -167,7 +183,7 @@ self.assertEqual(result.getvalue(), start + "") def test_xmlgen_content(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -179,7 +195,7 @@ self.assertEqual(result.getvalue(), start + "huhei") def test_xmlgen_pi(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -191,7 +207,7 @@ self.assertEqual(result.getvalue(), start + "") def test_xmlgen_content_escape(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -204,7 +220,7 @@ start + "<huhei&") def test_xmlgen_attr_escape(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -223,8 +239,41 @@ "" "")) + def test_xmlgen_encoding(self): + encodings = ('iso-8859-15', 'utf-8', + 'utf-16be', 'utf-16le', + 'utf-32be', 'utf-32le') + for encoding in encodings: + result = self.ioclass() + gen = XMLGenerator(result, encoding=encoding) + + gen.startDocument() + gen.startElement("doc", {"a": u'\u20ac'}) + gen.characters(u"\u20ac") + gen.endElement("doc") + gen.endDocument() + + self.assertEqual(result.getvalue(), ( + u'\n' + u'\u20ac' % encoding + ).encode(encoding, 'xmlcharrefreplace')) + + def test_xmlgen_unencodable(self): + result = self.ioclass() + gen = XMLGenerator(result, encoding='ascii') + + gen.startDocument() + gen.startElement("doc", {"a": u'\u20ac'}) + gen.characters(u"\u20ac") + gen.endElement("doc") + gen.endDocument() + + self.assertEqual(result.getvalue(), + '\n' + '') + def test_xmlgen_ignorable(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -236,7 +285,7 @@ self.assertEqual(result.getvalue(), start + " ") def test_xmlgen_ns(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -254,7 +303,7 @@ ns_uri)) def test_1463026_1(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -265,7 +314,7 @@ self.assertEqual(result.getvalue(), start+'') def test_1463026_2(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -278,7 +327,7 @@ self.assertEqual(result.getvalue(), start+'') def test_1463026_3(self): - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -294,7 +343,7 @@ def test_5027_1(self): # The xml prefix (as in xml:lang below) is reserved and bound by # definition to http://www.w3.org/XML/1998/namespace. XMLGenerator had - # a bug whereby a KeyError is thrown because this namespace is missing + # a bug whereby a KeyError is raised because this namespace is missing # from a dictionary. # # This test demonstrates the bug by parsing a document. @@ -306,7 +355,7 @@ parser = make_parser() parser.setFeature(feature_namespaces, True) - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) parser.setContentHandler(gen) parser.parse(test_xml) @@ -320,12 +369,12 @@ def test_5027_2(self): # The xml prefix (as in xml:lang below) is reserved and bound by # definition to http://www.w3.org/XML/1998/namespace. XMLGenerator had - # a bug whereby a KeyError is thrown because this namespace is missing + # a bug whereby a KeyError is raised because this namespace is missing # from a dictionary. # # This test demonstrates the bug by direct manipulation of the # XMLGenerator. - result = StringIO() + result = self.ioclass() gen = XMLGenerator(result) gen.startDocument() @@ -345,6 +394,44 @@ 'Hello' '')) + def test_no_close_file(self): + result = self.ioclass() + def func(out): + gen = XMLGenerator(out) + gen.startDocument() + gen.startElement("doc", {}) + func(result) + self.assertFalse(result.closed) + + def test_xmlgen_fragment(self): + result = self.ioclass() + gen = XMLGenerator(result) + + # Don't call gen.startDocument() + gen.startElement("foo", {"a": "1.0"}) + gen.characters("Hello") + gen.endElement("foo") + gen.startElement("bar", {"b": "2.0"}) + gen.endElement("bar") + # Don't call gen.endDocument() + + self.assertEqual(result.getvalue(), + 'Hello') + +class StringXmlgenTest(XmlgenTest, unittest.TestCase): + ioclass = StringIO + +class BytesIOXmlgenTest(XmlgenTest, unittest.TestCase): + ioclass = io.BytesIO + +class WriterXmlgenTest(XmlgenTest, unittest.TestCase): + class ioclass(list): + write = list.append + closed = False + + def getvalue(self): + return b''.join(self) + class XMLFilterBaseTest(unittest.TestCase): def test_filter_basic(self): @@ -384,6 +471,21 @@ self.assertEqual(result.getvalue(), xml_test_out) + @requires_unicode_filenames + def test_expat_file_unicode(self): + fname = support.TESTFN_UNICODE + shutil.copyfile(TEST_XMLFILE, fname) + self.addCleanup(support.unlink, fname) + + parser = create_parser() + result = StringIO() + xmlgen = XMLGenerator(result) + + parser.setContentHandler(xmlgen) + parser.parse(open(fname)) + + self.assertEqual(result.getvalue(), xml_test_out) + # ===== DTDHandler support class TestDTDHandler: @@ -523,6 +625,21 @@ self.assertEqual(result.getvalue(), xml_test_out) + @requires_unicode_filenames + def test_expat_inpsource_sysid_unicode(self): + fname = support.TESTFN_UNICODE + shutil.copyfile(TEST_XMLFILE, fname) + self.addCleanup(support.unlink, fname) + + parser = create_parser() + result = StringIO() + xmlgen = XMLGenerator(result) + + parser.setContentHandler(xmlgen) + parser.parse(InputSource(fname)) + + self.assertEqual(result.getvalue(), xml_test_out) + def test_expat_inpsource_stream(self): parser = create_parser() result = StringIO() @@ -596,6 +713,21 @@ self.assertEqual(parser.getSystemId(), TEST_XMLFILE) self.assertEqual(parser.getPublicId(), None) + @requires_unicode_filenames + def test_expat_locator_withinfo_unicode(self): + fname = support.TESTFN_UNICODE + shutil.copyfile(TEST_XMLFILE, fname) + self.addCleanup(support.unlink, fname) + + result = StringIO() + xmlgen = XMLGenerator(result) + parser = create_parser() + parser.setContentHandler(xmlgen) + parser.parse(fname) + + self.assertEqual(parser.getSystemId(), fname) + self.assertEqual(parser.getPublicId(), None) + # =========================================================================== # @@ -744,7 +876,9 @@ def test_main(): run_unittest(MakeParserTest, SaxutilsTest, - XmlgenTest, + StringXmlgenTest, + BytesIOXmlgenTest, + WriterXmlgenTest, ExpatReaderTest, ErrorReportingTest, XmlReaderTest) diff --git a/lib-python/2.7/test/test_select.py b/lib-python/2.7/test/test_select.py --- a/lib-python/2.7/test/test_select.py +++ b/lib-python/2.7/test/test_select.py @@ -49,6 +49,15 @@ self.fail('Unexpected return values from select():', rfd, wfd, xfd) p.close() + # Issue 16230: Crash on select resized list + def test_select_mutated(self): + a = [] + class F: + def fileno(self): + del a[-1] + return sys.__stdout__.fileno() + a[:] = [F()] * 10 + self.assertEqual(select.select([], a, []), ([], a[:5], [])) def test_main(): test_support.run_unittest(SelectTestCase) diff --git a/lib-python/2.7/test/test_shutil.py b/lib-python/2.7/test/test_shutil.py --- a/lib-python/2.7/test/test_shutil.py +++ b/lib-python/2.7/test/test_shutil.py @@ -7,6 +7,7 @@ import stat import os import os.path +import errno from os.path import splitdrive from distutils.spawn import find_executable, spawn from shutil import (_make_tarball, _make_zipfile, make_archive, @@ -339,6 +340,35 @@ shutil.rmtree(TESTFN, ignore_errors=True) shutil.rmtree(TESTFN2, ignore_errors=True) + @unittest.skipUnless(hasattr(os, 'chflags') and + hasattr(errno, 'EOPNOTSUPP') and + hasattr(errno, 'ENOTSUP'), + "requires os.chflags, EOPNOTSUPP & ENOTSUP") + def test_copystat_handles_harmless_chflags_errors(self): + tmpdir = self.mkdtemp() + file1 = os.path.join(tmpdir, 'file1') + file2 = os.path.join(tmpdir, 'file2') + self.write_file(file1, 'xxx') + self.write_file(file2, 'xxx') + + def make_chflags_raiser(err): + ex = OSError() + + def _chflags_raiser(path, flags): + ex.errno = err + raise ex + return _chflags_raiser + old_chflags = os.chflags + try: + for err in errno.EOPNOTSUPP, errno.ENOTSUP: + os.chflags = make_chflags_raiser(err) + shutil.copystat(file1, file2) + # assert others errors break it + os.chflags = make_chflags_raiser(errno.EOPNOTSUPP + errno.ENOTSUP) + self.assertRaises(OSError, shutil.copystat, file1, file2) + finally: + os.chflags = old_chflags + @unittest.skipUnless(zlib, "requires zlib") def test_make_tarball(self): # creating something to tar diff --git a/lib-python/2.7/test/test_signal.py b/lib-python/2.7/test/test_signal.py --- a/lib-python/2.7/test/test_signal.py +++ b/lib-python/2.7/test/test_signal.py @@ -109,7 +109,7 @@ # This wait should be interrupted by the signal's exception. self.wait(child) time.sleep(1) # Give the signal time to be delivered. - self.fail('HandlerBCalled exception not thrown') + self.fail('HandlerBCalled exception not raised') except HandlerBCalled: self.assertTrue(self.b_called) self.assertFalse(self.a_called) @@ -148,7 +148,7 @@ # test-running process from all the signals. It then # communicates with that child process over a pipe and # re-raises information about any exceptions the child - # throws. The real work happens in self.run_test(). + # raises. The real work happens in self.run_test(). os_done_r, os_done_w = os.pipe() with closing(os.fdopen(os_done_r)) as done_r, \ closing(os.fdopen(os_done_w, 'w')) as done_w: @@ -227,6 +227,13 @@ signal.signal(7, handler) +class WakeupFDTests(unittest.TestCase): + + def test_invalid_fd(self): + fd = test_support.make_bad_fd() + self.assertRaises(ValueError, signal.set_wakeup_fd, fd) + + @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") class WakeupSignalTests(unittest.TestCase): TIMEOUT_FULL = 10 @@ -485,8 +492,9 @@ def test_main(): test_support.run_unittest(BasicSignalTests, InterProcessSignalTests, - WakeupSignalTests, SiginterruptTest, - ItimerTest, WindowsSignalTests) + WakeupFDTests, WakeupSignalTests, + SiginterruptTest, ItimerTest, + WindowsSignalTests) if __name__ == "__main__": diff --git a/lib-python/2.7/test/test_socket.py b/lib-python/2.7/test/test_socket.py --- a/lib-python/2.7/test/test_socket.py +++ b/lib-python/2.7/test/test_socket.py @@ -6,6 +6,7 @@ import errno import socket import select +import _testcapi import time import traceback import Queue @@ -644,9 +645,10 @@ if SUPPORTS_IPV6: socket.getaddrinfo('::1', 80) # port can be a string service name such as "http", a numeric - # port number or None + # port number (int or long), or None socket.getaddrinfo(HOST, "http") socket.getaddrinfo(HOST, 80) + socket.getaddrinfo(HOST, 80L) socket.getaddrinfo(HOST, None) # test family and socktype filters infos = socket.getaddrinfo(HOST, None, socket.AF_INET) @@ -699,11 +701,17 @@ def test_sendall_interrupted_with_timeout(self): self.check_sendall_interrupted(True) - def testListenBacklog0(self): + def test_listen_backlog(self): + for backlog in 0, -1: + srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + srv.bind((HOST, 0)) + srv.listen(backlog) + srv.close() + + # Issue 15989 srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.bind((HOST, 0)) - # backlog = 0 - srv.listen(0) + self.assertRaises(OverflowError, srv.listen, _testcapi.INT_MAX + 1) srv.close() @unittest.skipUnless(SUPPORTS_IPV6, 'IPv6 required for this test.') @@ -807,6 +815,11 @@ def _testShutdown(self): self.serv_conn.send(MSG) + # Issue 15989 + self.assertRaises(OverflowError, self.serv_conn.shutdown, + _testcapi.INT_MAX + 1) + self.assertRaises(OverflowError, self.serv_conn.shutdown, + 2 + (_testcapi.UINT_MAX + 1)) self.serv_conn.shutdown(2) @unittest.skipUnless(thread, 'Threading required for this test.') @@ -882,7 +895,10 @@ def testSetBlocking(self): # Testing whether set blocking works - self.serv.setblocking(0) + self.serv.setblocking(True) + self.assertIsNone(self.serv.gettimeout()) + self.serv.setblocking(False) + self.assertEqual(self.serv.gettimeout(), 0.0) start = time.time() try: self.serv.accept() @@ -890,6 +906,10 @@ pass end = time.time() self.assertTrue((end - start) < 1.0, "Error setting non-blocking mode.") + # Issue 15989 + if _testcapi.UINT_MAX < _testcapi.ULONG_MAX: + self.serv.setblocking(_testcapi.UINT_MAX + 1) + self.assertIsNone(self.serv.gettimeout()) def _testSetBlocking(self): pass @@ -961,8 +981,8 @@ def tearDown(self): self.serv_file.close() self.assertTrue(self.serv_file.closed) + SocketConnectedTest.tearDown(self) self.serv_file = None - SocketConnectedTest.tearDown(self) def clientSetUp(self): SocketConnectedTest.clientSetUp(self) @@ -1150,6 +1170,64 @@ bufsize = 1 # Default-buffered for reading; line-buffered for writing + class SocketMemo(object): + """A wrapper to keep track of sent data, needed to examine write behaviour""" + def __init__(self, sock): + self._sock = sock + self.sent = [] + + def send(self, data, flags=0): + n = self._sock.send(data, flags) + self.sent.append(data[:n]) + return n + + def sendall(self, data, flags=0): + self._sock.sendall(data, flags) + self.sent.append(data) + + def __getattr__(self, attr): + return getattr(self._sock, attr) + + def getsent(self): + return [e.tobytes() if isinstance(e, memoryview) else e for e in self.sent] + + def setUp(self): + FileObjectClassTestCase.setUp(self) + self.serv_file._sock = self.SocketMemo(self.serv_file._sock) + + def testLinebufferedWrite(self): + # Write two lines, in small chunks + msg = MSG.strip() + print >> self.serv_file, msg, + print >> self.serv_file, msg + + # second line: + print >> self.serv_file, msg, + print >> self.serv_file, msg, + print >> self.serv_file, msg + + # third line + print >> self.serv_file, '' + + self.serv_file.flush() + + msg1 = "%s %s\n"%(msg, msg) + msg2 = "%s %s %s\n"%(msg, msg, msg) + msg3 = "\n" + self.assertEqual(self.serv_file._sock.getsent(), [msg1, msg2, msg3]) + + def _testLinebufferedWrite(self): + msg = MSG.strip() + msg1 = "%s %s\n"%(msg, msg) + msg2 = "%s %s %s\n"%(msg, msg, msg) + msg3 = "\n" + l1 = self.cli_file.readline() + self.assertEqual(l1, msg1) + l2 = self.cli_file.readline() + self.assertEqual(l2, msg2) + l3 = self.cli_file.readline() + self.assertEqual(l3, msg3) + class SmallBufferedFileObjectClassTestCase(FileObjectClassTestCase): @@ -1197,7 +1275,26 @@ port = test_support.find_unused_port() with self.assertRaises(socket.error) as cm: socket.create_connection((HOST, port)) - self.assertEqual(cm.exception.errno, errno.ECONNREFUSED) + + # Issue #16257: create_connection() calls getaddrinfo() against + # 'localhost'. This may result in an IPV6 addr being returned + # as well as an IPV4 one: + # >>> socket.getaddrinfo('localhost', port, 0, SOCK_STREAM) + # >>> [(2, 2, 0, '', ('127.0.0.1', 41230)), + # (26, 2, 0, '', ('::1', 41230, 0, 0))] + # + # create_connection() enumerates through all the addresses returned + # and if it doesn't successfully bind to any of them, it propagates + # the last exception it encountered. + # + # On Solaris, ENETUNREACH is returned in this circumstance instead + # of ECONNREFUSED. So, if that errno exists, add it to our list of + # expected errnos. + expected_errnos = [ errno.ECONNREFUSED, ] + if hasattr(errno, 'ENETUNREACH'): + expected_errnos.append(errno.ENETUNREACH) + + self.assertIn(cm.exception.errno, expected_errnos) def test_create_connection_timeout(self): # Issue #9792: create_connection() should not recast timeout errors diff --git a/lib-python/2.7/test/test_socketserver.py b/lib-python/2.7/test/test_socketserver.py --- a/lib-python/2.7/test/test_socketserver.py +++ b/lib-python/2.7/test/test_socketserver.py @@ -8,6 +8,8 @@ import select import signal import socket +import select +import errno import tempfile import unittest import SocketServer @@ -32,8 +34,11 @@ if hasattr(signal, 'alarm'): signal.alarm(n) +# Remember real select() to avoid interferences with mocking +_real_select = select.select + def receive(sock, n, timeout=20): - r, w, x = select.select([sock], [], [], timeout) + r, w, x = _real_select([sock], [], [], timeout) if sock in r: return sock.recv(n) else: @@ -53,7 +58,7 @@ def simple_subprocess(testcase): pid = os.fork() if pid == 0: - # Don't throw an exception; it would be caught by the test harness. + # Don't raise an exception; it would be caught by the test harness. os._exit(72) yield None pid2, status = os.waitpid(pid, 0) @@ -225,6 +230,38 @@ SocketServer.DatagramRequestHandler, self.dgram_examine) + @contextlib.contextmanager + def mocked_select_module(self): + """Mocks the select.select() call to raise EINTR for first call""" + old_select = select.select + + class MockSelect: + def __init__(self): + self.called = 0 + + def __call__(self, *args): + self.called += 1 + if self.called == 1: + # raise the exception on first call + raise select.error(errno.EINTR, os.strerror(errno.EINTR)) + else: + # Return real select value for consecutive calls + return old_select(*args) + + select.select = MockSelect() + try: + yield select.select + finally: + select.select = old_select + + def test_InterruptServerSelectCall(self): + with self.mocked_select_module() as mock_select: + pid = self.run_server(SocketServer.TCPServer, + SocketServer.StreamRequestHandler, + self.stream_examine) + # Make sure select was called again: + self.assertGreater(mock_select.called, 1) + # Alas, on Linux (at least) recvfrom() doesn't return a meaningful # client address so this cannot work: diff --git a/lib-python/2.7/test/test_ssl.py b/lib-python/2.7/test/test_ssl.py --- a/lib-python/2.7/test/test_ssl.py +++ b/lib-python/2.7/test/test_ssl.py @@ -95,12 +95,8 @@ sys.stdout.write("\n RAND_status is %d (%s)\n" % (v, (v and "sufficient randomness") or "insufficient randomness")) - try: - ssl.RAND_egd(1) - except TypeError: - pass - else: - print "didn't raise TypeError" + self.assertRaises(TypeError, ssl.RAND_egd, 1) + self.assertRaises(TypeError, ssl.RAND_egd, 'foo', 1) ssl.RAND_add("this is a random string", 75.0) def test_parse_cert(self): @@ -111,13 +107,12 @@ if test_support.verbose: sys.stdout.write("\n" + pprint.pformat(p) + "\n") self.assertEqual(p['subject'], - ((('countryName', u'US'),), - (('stateOrProvinceName', u'Delaware'),), - (('localityName', u'Wilmington'),), - (('organizationName', u'Python Software Foundation'),), - (('organizationalUnitName', u'SSL'),), - (('commonName', u'somemachine.python.org'),)), + ((('countryName', 'XY'),), + (('localityName', 'Castle Anthrax'),), + (('organizationName', 'Python Software Foundation'),), + (('commonName', 'localhost'),)) ) + self.assertEqual(p['subjectAltName'], (('DNS', 'localhost'),)) # Issue #13034: the subjectAltName in some certificates # (notably projects.developer.nokia.com:443) wasn't parsed p = ssl._ssl._test_decode_cert(NOKIACERT) @@ -284,6 +279,34 @@ finally: s.close() + def test_timeout_connect_ex(self): + # Issue #12065: on a timeout, connect_ex() should return the original + # errno (mimicking the behaviour of non-SSL sockets). + with test_support.transient_internet("svn.python.org"): + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=SVN_PYTHON_ORG_ROOT_CERT, + do_handshake_on_connect=False) + try: + s.settimeout(0.0000001) + rc = s.connect_ex(('svn.python.org', 443)) + if rc == 0: + self.skipTest("svn.python.org responded too quickly") + self.assertIn(rc, (errno.EAGAIN, errno.EWOULDBLOCK)) + finally: + s.close() + + def test_connect_ex_error(self): + with test_support.transient_internet("svn.python.org"): + s = ssl.wrap_socket(socket.socket(socket.AF_INET), + cert_reqs=ssl.CERT_REQUIRED, + ca_certs=SVN_PYTHON_ORG_ROOT_CERT) + try: + self.assertEqual(errno.ECONNREFUSED, + s.connect_ex(("svn.python.org", 444))) + finally: + s.close() + @unittest.skipIf(os.name == "nt", "Can't use a socket as a file under Windows") def test_makefile_close(self): # Issue #5238: creating a file-like object with makefile() shouldn't @@ -355,7 +378,8 @@ # SHA256 was added in OpenSSL 0.9.8 if ssl.OPENSSL_VERSION_INFO < (0, 9, 8, 0, 15): self.skipTest("SHA256 not available on %r" % ssl.OPENSSL_VERSION) - # NOTE: https://sha256.tbs-internet.com is another possible test host + self.skipTest("remote host needs SNI, only available on Python 3.2+") + # NOTE: https://sha2.hboeck.de is another possible test host remote = ("sha256.tbs-internet.com", 443) sha256_cert = os.path.join(os.path.dirname(__file__), "sha256.pem") with test_support.transient_internet("sha256.tbs-internet.com"): diff --git a/lib-python/2.7/test/test_str.py b/lib-python/2.7/test/test_str.py --- a/lib-python/2.7/test/test_str.py +++ b/lib-python/2.7/test/test_str.py @@ -35,6 +35,18 @@ string_tests.MixinStrUnicodeUserStringTest.test_formatting(self) self.assertRaises(OverflowError, '%c'.__mod__, 0x1234) + @test_support.cpython_only + def test_formatting_huge_precision(self): + from _testcapi import INT_MAX + format_string = "%.{}f".format(INT_MAX + 1) + with self.assertRaises(ValueError): + result = format_string % 2.34 + + def test_formatting_huge_width(self): + format_string = "%{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format_string % 2.34 + def test_conversion(self): # Make sure __str__() behaves properly class Foo0: @@ -371,6 +383,21 @@ self.assertRaises(ValueError, format, "", "-") self.assertRaises(ValueError, "{0:=s}".format, '') + def test_format_huge_precision(self): + format_string = ".{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format(2.34, format_string) + + def test_format_huge_width(self): + format_string = "{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format(2.34, format_string) + + def test_format_huge_item_number(self): + format_string = "{{{}:.6f}}".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format_string.format(2.34) + def test_format_auto_numbering(self): class C: def __init__(self, x=100): diff --git a/lib-python/2.7/test/test_strptime.py b/lib-python/2.7/test/test_strptime.py --- a/lib-python/2.7/test/test_strptime.py +++ b/lib-python/2.7/test/test_strptime.py @@ -378,6 +378,14 @@ need_escaping = ".^$*+?{}\[]|)(" self.assertTrue(_strptime._strptime_time(need_escaping, need_escaping)) + def test_feb29_on_leap_year_without_year(self): + time.strptime("Feb 29", "%b %d") + + def test_mar1_comes_after_feb29_even_when_omitting_the_year(self): + self.assertLess( + time.strptime("Feb 29", "%b %d"), + time.strptime("Mar 1", "%b %d")) + class Strptime12AMPMTests(unittest.TestCase): """Test a _strptime regression in '%I %p' at 12 noon (12 PM)""" diff --git a/lib-python/2.7/test/test_struct.py b/lib-python/2.7/test/test_struct.py --- a/lib-python/2.7/test/test_struct.py +++ b/lib-python/2.7/test/test_struct.py @@ -3,7 +3,8 @@ import unittest import struct import inspect -from test.test_support import run_unittest, check_warnings, check_py3k_warnings +from test import test_support as support +from test.test_support import (check_warnings, check_py3k_warnings) import sys ISBIGENDIAN = sys.byteorder == "big" @@ -544,8 +545,29 @@ hugecount2 = '{}b{}H'.format(sys.maxsize//2, sys.maxsize//2) self.assertRaises(struct.error, struct.calcsize, hugecount2) + def check_sizeof(self, format_str, number_of_codes): + # The size of 'PyStructObject' + totalsize = support.calcobjsize('5P') + # The size taken up by the 'formatcode' dynamic array + totalsize += struct.calcsize('3P') * (number_of_codes + 1) + support.check_sizeof(self, struct.Struct(format_str), totalsize) + + @support.cpython_only + def test__sizeof__(self): + for code in integer_codes: + self.check_sizeof(code, 1) + self.check_sizeof('BHILfdspP', 9) + self.check_sizeof('B' * 1234, 1234) + self.check_sizeof('fd', 2) + self.check_sizeof('xxxxxxxxxxxxxx', 0) + self.check_sizeof('100H', 100) + self.check_sizeof('187s', 1) + self.check_sizeof('20p', 1) + self.check_sizeof('0s', 1) + self.check_sizeof('0c', 0) + def test_main(): - run_unittest(StructTest) + support.run_unittest(StructTest) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_subprocess.py b/lib-python/2.7/test/test_subprocess.py --- a/lib-python/2.7/test/test_subprocess.py +++ b/lib-python/2.7/test/test_subprocess.py @@ -58,6 +58,18 @@ self.assertEqual(actual, expected, msg) +class PopenTestException(Exception): + pass + + +class PopenExecuteChildRaises(subprocess.Popen): + """Popen subclass for testing cleanup of subprocess.PIPE filehandles when + _execute_child fails. + """ + def _execute_child(self, *args, **kwargs): + raise PopenTestException("Forced Exception for Test") + + class ProcessTestCase(BaseTestCase): def test_call_seq(self): @@ -526,6 +538,7 @@ finally: for h in handles: os.close(h) + test_support.unlink(test_support.TESTFN) def test_list2cmdline(self): self.assertEqual(subprocess.list2cmdline(['a b c', 'd', 'e']), @@ -631,6 +644,27 @@ time.sleep(2) p.communicate("x" * 2**20) + # This test is Linux-ish specific for simplicity to at least have + # some coverage. It is not a platform specific bug. + @unittest.skipUnless(os.path.isdir('/proc/%d/fd' % os.getpid()), + "Linux specific") + def test_failed_child_execute_fd_leak(self): + """Test for the fork() failure fd leak reported in issue16327.""" + fd_directory = '/proc/%d/fd' % os.getpid() + fds_before_popen = os.listdir(fd_directory) + with self.assertRaises(PopenTestException): + PopenExecuteChildRaises( + [sys.executable, '-c', 'pass'], stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # NOTE: This test doesn't verify that the real _execute_child + # does not close the file descriptors itself on the way out + # during an exception. Code inspection has confirmed that. + + fds_after_exception = os.listdir(fd_directory) + self.assertEqual(fds_before_popen, fds_after_exception) + + # context manager class _SuppressCoreFiles(object): """Try to prevent core files from being created.""" @@ -717,6 +751,52 @@ self.addCleanup(p.stdout.close) self.assertEqual(p.stdout.read(), "apple") + class _TestExecuteChildPopen(subprocess.Popen): + """Used to test behavior at the end of _execute_child.""" + def __init__(self, testcase, *args, **kwargs): + self._testcase = testcase + subprocess.Popen.__init__(self, *args, **kwargs) + + def _execute_child( + self, args, executable, preexec_fn, close_fds, cwd, env, + universal_newlines, startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + try: + subprocess.Popen._execute_child( + self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + finally: + # Open a bunch of file descriptors and verify that + # none of them are the same as the ones the Popen + # instance is using for stdin/stdout/stderr. + devzero_fds = [os.open("/dev/zero", os.O_RDONLY) + for _ in range(8)] + try: + for fd in devzero_fds: + self._testcase.assertNotIn( + fd, (p2cwrite, c2pread, errread)) + finally: + map(os.close, devzero_fds) + + @unittest.skipIf(not os.path.exists("/dev/zero"), "/dev/zero required.") + def test_preexec_errpipe_does_not_double_close_pipes(self): + """Issue16140: Don't double close pipes on preexec error.""" + + def raise_it(): + raise RuntimeError("force the _execute_child() errpipe_data path.") + + with self.assertRaises(RuntimeError): + self._TestExecuteChildPopen( + self, [sys.executable, "-c", "pass"], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, preexec_fn=raise_it) + def test_args_string(self): # args is a string f, fname = mkstemp() @@ -812,6 +892,8 @@ getattr(p, method)(*args) return p + @unittest.skipIf(sys.platform.startswith(('netbsd', 'openbsd')), + "Due to known OS bug (issue #16762)") def _kill_dead_process(self, method, *args): # Do not inherit file handles from the parent. # It should fix failures on some platforms. diff --git a/lib-python/2.7/test/test_support.py b/lib-python/2.7/test/test_support.py --- a/lib-python/2.7/test/test_support.py +++ b/lib-python/2.7/test/test_support.py @@ -18,6 +18,9 @@ import UserDict import re import time +import struct +import _testcapi +import sysconfig try: import thread except ImportError: @@ -179,15 +182,79 @@ except KeyError: pass +if sys.platform.startswith("win"): + def _waitfor(func, pathname, waitall=False): + # Peform the operation + func(pathname) + # Now setup the wait loop + if waitall: + dirname = pathname + else: + dirname, name = os.path.split(pathname) + dirname = dirname or '.' + # Check for `pathname` to be removed from the filesystem. + # The exponential backoff of the timeout amounts to a total + # of ~1 second after which the deletion is probably an error + # anyway. + # Testing on a i7 at 4.3GHz shows that usually only 1 iteration is + # required when contention occurs. + timeout = 0.001 + while timeout < 1.0: + # Note we are only testing for the existance of the file(s) in + # the contents of the directory regardless of any security or + # access rights. If we have made it this far, we have sufficient + # permissions to do that much using Python's equivalent of the + # Windows API FindFirstFile. + # Other Windows APIs can fail or give incorrect results when + # dealing with files that are pending deletion. + L = os.listdir(dirname) + if not (L if waitall else name in L): + return + # Increase the timeout and try again + time.sleep(timeout) + timeout *= 2 + warnings.warn('tests may fail, delete still pending for ' + pathname, + RuntimeWarning, stacklevel=4) + + def _unlink(filename): + _waitfor(os.unlink, filename) + + def _rmdir(dirname): + _waitfor(os.rmdir, dirname) + + def _rmtree(path): + def _rmtree_inner(path): + for name in os.listdir(path): + fullname = os.path.join(path, name) + if os.path.isdir(fullname): + _waitfor(_rmtree_inner, fullname, waitall=True) + os.rmdir(fullname) + else: + os.unlink(fullname) + _waitfor(_rmtree_inner, path, waitall=True) + _waitfor(os.rmdir, path) +else: + _unlink = os.unlink + _rmdir = os.rmdir + _rmtree = shutil.rmtree + def unlink(filename): try: - os.unlink(filename) + _unlink(filename) except OSError: pass +def rmdir(dirname): + try: + _rmdir(dirname) + except OSError as error: + # The directory need not exist. + if error.errno != errno.ENOENT: + raise + def rmtree(path): try: - shutil.rmtree(path) + _rmtree(path) except OSError, e: # Unix returns ENOENT, Windows returns ESRCH. if e.errno not in (errno.ENOENT, errno.ESRCH): @@ -405,7 +472,7 @@ the CWD, an error is raised. If it's True, only a warning is raised and the original CWD is used. """ - if isinstance(name, unicode): + if have_unicode and isinstance(name, unicode): try: name = name.encode(sys.getfilesystemencoding() or 'ascii') except UnicodeEncodeError: @@ -767,6 +834,9 @@ ('EAI_FAIL', -4), ('EAI_NONAME', -2), ('EAI_NODATA', -5), + # Windows defines EAI_NODATA as 11001 but idiotic getaddrinfo() + # implementation actually returns WSANO_DATA i.e. 11004. + ('WSANO_DATA', 11004), ] denied = ResourceDenied("Resource '%s' is not available" % resource_name) @@ -858,6 +928,32 @@ gc.collect() +_header = '2P' +if hasattr(sys, "gettotalrefcount"): + _header = '2P' + _header +_vheader = _header + 'P' + +def calcobjsize(fmt): + return struct.calcsize(_header + fmt + '0P') + +def calcvobjsize(fmt): + return struct.calcsize(_vheader + fmt + '0P') + + +_TPFLAGS_HAVE_GC = 1<<14 +_TPFLAGS_HEAPTYPE = 1<<9 + +def check_sizeof(test, o, size): + result = sys.getsizeof(o) + # add GC header size + if ((type(o) == type) and (o.__flags__ & _TPFLAGS_HEAPTYPE) or\ + ((type(o) != type) and (type(o).__flags__ & _TPFLAGS_HAVE_GC))): + size += _testcapi.SIZEOF_PYGC_HEAD + msg = 'wrong size for %s: got %d, expected %d' \ + % (type(o), result, size) + test.assertEqual(result, size, msg) + + #======================================================================= # Decorator for running a function in a different locale, correctly resetting # it afterwards. @@ -966,7 +1062,7 @@ return wrapper return decorator -def precisionbigmemtest(size, memuse, overhead=5*_1M): +def precisionbigmemtest(size, memuse, overhead=5*_1M, dry_run=True): def decorator(f): def wrapper(self): if not real_max_memuse: @@ -974,11 +1070,12 @@ else: maxsize = size - if real_max_memuse and real_max_memuse < maxsize * memuse: - if verbose: - sys.stderr.write("Skipping %s because of memory " - "constraint\n" % (f.__name__,)) - return + if ((real_max_memuse or not dry_run) + and real_max_memuse < maxsize * memuse): + if verbose: + sys.stderr.write("Skipping %s because of memory " + "constraint\n" % (f.__name__,)) + return return f(self, maxsize) wrapper.size = size @@ -1093,6 +1190,16 @@ suite.addTest(unittest.makeSuite(cls)) _run_suite(suite) +#======================================================================= +# Check for the presence of docstrings. + +HAVE_DOCSTRINGS = (check_impl_detail(cpython=False) or + sys.platform == 'win32' or + sysconfig.get_config_var('WITH_DOC_STRINGS')) + +requires_docstrings = unittest.skipUnless(HAVE_DOCSTRINGS, + "test requires docstrings") + #======================================================================= # doctest driver. @@ -1192,6 +1299,33 @@ except: break + at contextlib.contextmanager +def swap_attr(obj, attr, new_val): + """Temporary swap out an attribute with a new object. + + Usage: + with swap_attr(obj, "attr", 5): + ... + + This will set obj.attr to 5 for the duration of the with: block, + restoring the old value at the end of the block. If `attr` doesn't + exist on `obj`, it will be created and then deleted at the end of the + block. + """ + if hasattr(obj, attr): + real_val = getattr(obj, attr) + setattr(obj, attr, new_val) + try: + yield + finally: + setattr(obj, attr, real_val) + else: + setattr(obj, attr, new_val) + try: + yield + finally: + delattr(obj, attr) + def py3k_bytes(b): """Emulate the py3k bytes() constructor. diff --git a/lib-python/2.7/test/test_sys.py b/lib-python/2.7/test/test_sys.py --- a/lib-python/2.7/test/test_sys.py +++ b/lib-python/2.7/test/test_sys.py @@ -490,22 +490,8 @@ class SizeofTest(unittest.TestCase): - TPFLAGS_HAVE_GC = 1<<14 - TPFLAGS_HEAPTYPE = 1L<<9 - def setUp(self): - self.c = len(struct.pack('c', ' ')) - self.H = len(struct.pack('H', 0)) - self.i = len(struct.pack('i', 0)) - self.l = len(struct.pack('l', 0)) - self.P = len(struct.pack('P', 0)) - # due to missing size_t information from struct, it is assumed that - # sizeof(Py_ssize_t) = sizeof(void*) - self.header = 'PP' - self.vheader = self.header + 'P' - if hasattr(sys, "gettotalrefcount"): - self.header += '2P' - self.vheader += '2P' + self.P = struct.calcsize('P') self.longdigit = sys.long_info.sizeof_digit import _testcapi self.gc_headsize = _testcapi.SIZEOF_PYGC_HEAD @@ -515,128 +501,109 @@ self.file.close() test.test_support.unlink(test.test_support.TESTFN) - def check_sizeof(self, o, size): - result = sys.getsizeof(o) - if ((type(o) == type) and (o.__flags__ & self.TPFLAGS_HEAPTYPE) or\ - ((type(o) != type) and (type(o).__flags__ & self.TPFLAGS_HAVE_GC))): - size += self.gc_headsize - msg = 'wrong size for %s: got %d, expected %d' \ - % (type(o), result, size) - self.assertEqual(result, size, msg) - - def calcsize(self, fmt): - """Wrapper around struct.calcsize which enforces the alignment of the - end of a structure to the alignment requirement of pointer. - - Note: This wrapper should only be used if a pointer member is included - and no member with a size larger than a pointer exists. - """ - return struct.calcsize(fmt + '0P') + check_sizeof = test.test_support.check_sizeof def test_gc_head_size(self): # Check that the gc header size is added to objects tracked by the gc. - h = self.header - size = self.calcsize + size = test.test_support.calcobjsize gc_header_size = self.gc_headsize # bool objects are not gc tracked - self.assertEqual(sys.getsizeof(True), size(h + 'l')) + self.assertEqual(sys.getsizeof(True), size('l')) # but lists are - self.assertEqual(sys.getsizeof([]), size(h + 'P PP') + gc_header_size) + self.assertEqual(sys.getsizeof([]), size('P PP') + gc_header_size) def test_default(self): - h = self.header - size = self.calcsize - self.assertEqual(sys.getsizeof(True, -1), size(h + 'l')) + size = test.test_support.calcobjsize + self.assertEqual(sys.getsizeof(True, -1), size('l')) def test_objecttypes(self): # check all types defined in Objects/ - h = self.header - vh = self.vheader - size = self.calcsize + size = test.test_support.calcobjsize + vsize = test.test_support.calcvobjsize check = self.check_sizeof # bool - check(True, size(h + 'l')) + check(True, size('l')) # buffer with test.test_support.check_py3k_warnings(): - check(buffer(''), size(h + '2P2Pil')) + check(buffer(''), size('2P2Pil')) # builtin_function_or_method - check(len, size(h + '3P')) + check(len, size('3P')) # bytearray samples = ['', 'u'*100000] for sample in samples: x = bytearray(sample) - check(x, size(vh + 'iPP') + x.__alloc__() * self.c) + check(x, vsize('iPP') + x.__alloc__()) # bytearray_iterator - check(iter(bytearray()), size(h + 'PP')) + check(iter(bytearray()), size('PP')) # cell def get_cell(): x = 42 def inner(): return x return inner - check(get_cell().func_closure[0], size(h + 'P')) + check(get_cell().func_closure[0], size('P')) # classobj (old-style class) class class_oldstyle(): def method(): pass - check(class_oldstyle, size(h + '7P')) + check(class_oldstyle, size('7P')) # instance (old-style class) - check(class_oldstyle(), size(h + '3P')) + check(class_oldstyle(), size('3P')) # instancemethod (old-style class) - check(class_oldstyle().method, size(h + '4P')) + check(class_oldstyle().method, size('4P')) # complex - check(complex(0,1), size(h + '2d')) + check(complex(0,1), size('2d')) # code - check(get_cell().func_code, size(h + '4i8Pi3P')) + check(get_cell().func_code, size('4i8Pi3P')) # BaseException - check(BaseException(), size(h + '3P')) + check(BaseException(), size('3P')) # UnicodeEncodeError - check(UnicodeEncodeError("", u"", 0, 0, ""), size(h + '5P2PP')) + check(UnicodeEncodeError("", u"", 0, 0, ""), size('5P2PP')) # UnicodeDecodeError - check(UnicodeDecodeError("", "", 0, 0, ""), size(h + '5P2PP')) + check(UnicodeDecodeError("", "", 0, 0, ""), size('5P2PP')) # UnicodeTranslateError - check(UnicodeTranslateError(u"", 0, 1, ""), size(h + '5P2PP')) + check(UnicodeTranslateError(u"", 0, 1, ""), size('5P2PP')) # method_descriptor (descriptor object) - check(str.lower, size(h + '2PP')) + check(str.lower, size('2PP')) # classmethod_descriptor (descriptor object) # XXX # member_descriptor (descriptor object) import datetime - check(datetime.timedelta.days, size(h + '2PP')) + check(datetime.timedelta.days, size('2PP')) # getset_descriptor (descriptor object) import __builtin__ - check(__builtin__.file.closed, size(h + '2PP')) + check(__builtin__.file.closed, size('2PP')) # wrapper_descriptor (descriptor object) - check(int.__add__, size(h + '2P2P')) + check(int.__add__, size('2P2P')) # dictproxy class C(object): pass - check(C.__dict__, size(h + 'P')) + check(C.__dict__, size('P')) # method-wrapper (descriptor object) - check({}.__iter__, size(h + '2P')) + check({}.__iter__, size('2P')) # dict - check({}, size(h + '3P2P' + 8*'P2P')) + check({}, size('3P2P' + 8*'P2P')) x = {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8} - check(x, size(h + '3P2P' + 8*'P2P') + 16*size('P2P')) + check(x, size('3P2P' + 8*'P2P') + 16*struct.calcsize('P2P')) # dictionary-keyiterator - check({}.iterkeys(), size(h + 'P2PPP')) + check({}.iterkeys(), size('P2PPP')) # dictionary-valueiterator - check({}.itervalues(), size(h + 'P2PPP')) + check({}.itervalues(), size('P2PPP')) # dictionary-itemiterator - check({}.iteritems(), size(h + 'P2PPP')) + check({}.iteritems(), size('P2PPP')) # ellipses - check(Ellipsis, size(h + '')) + check(Ellipsis, size('')) # EncodingMap import codecs, encodings.iso8859_3 x = codecs.charmap_build(encodings.iso8859_3.decoding_table) - check(x, size(h + '32B2iB')) + check(x, size('32B2iB')) # enumerate - check(enumerate([]), size(h + 'l3P')) + check(enumerate([]), size('l3P')) # file - check(self.file, size(h + '4P2i4P3i3P3i')) + check(self.file, size('4P2i4P3i3P3i')) # float - check(float(0), size(h + 'd')) + check(float(0), size('d')) # sys.floatinfo - check(sys.float_info, size(vh) + self.P * len(sys.float_info)) + check(sys.float_info, vsize('') + self.P * len(sys.float_info)) # frame import inspect CO_MAXBLOCKS = 20 @@ -645,10 +612,10 @@ nfrees = len(x.f_code.co_freevars) extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\ ncells + nfrees - 1 - check(x, size(vh + '12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) + check(x, vsize('12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) # function def func(): pass - check(func, size(h + '9P')) + check(func, size('9P')) class c(): @staticmethod def foo(): @@ -657,65 +624,65 @@ def bar(cls): pass # staticmethod - check(foo, size(h + 'P')) + check(foo, size('P')) # classmethod - check(bar, size(h + 'P')) + check(bar, size('P')) # generator def get_gen(): yield 1 - check(get_gen(), size(h + 'Pi2P')) + check(get_gen(), size('Pi2P')) # integer - check(1, size(h + 'l')) - check(100, size(h + 'l')) + check(1, size('l')) + check(100, size('l')) # iterator - check(iter('abc'), size(h + 'lP')) + check(iter('abc'), size('lP')) # callable-iterator import re - check(re.finditer('',''), size(h + '2P')) + check(re.finditer('',''), size('2P')) # list samples = [[], [1,2,3], ['1', '2', '3']] for sample in samples: - check(sample, size(vh + 'PP') + len(sample)*self.P) + check(sample, vsize('PP') + len(sample)*self.P) # sortwrapper (list) # XXX # cmpwrapper (list) # XXX # listiterator (list) - check(iter([]), size(h + 'lP')) + check(iter([]), size('lP')) # listreverseiterator (list) - check(reversed([]), size(h + 'lP')) + check(reversed([]), size('lP')) # long - check(0L, size(vh)) - check(1L, size(vh) + self.longdigit) - check(-1L, size(vh) + self.longdigit) + check(0L, vsize('')) + check(1L, vsize('') + self.longdigit) + check(-1L, vsize('') + self.longdigit) PyLong_BASE = 2**sys.long_info.bits_per_digit - check(long(PyLong_BASE), size(vh) + 2*self.longdigit) - check(long(PyLong_BASE**2-1), size(vh) + 2*self.longdigit) - check(long(PyLong_BASE**2), size(vh) + 3*self.longdigit) + check(long(PyLong_BASE), vsize('') + 2*self.longdigit) + check(long(PyLong_BASE**2-1), vsize('') + 2*self.longdigit) + check(long(PyLong_BASE**2), vsize('') + 3*self.longdigit) # module - check(unittest, size(h + 'P')) + check(unittest, size('P')) # None - check(None, size(h + '')) + check(None, size('')) # object - check(object(), size(h + '')) + check(object(), size('')) # property (descriptor object) class C(object): def getx(self): return self.__x def setx(self, value): self.__x = value def delx(self): del self.__x x = property(getx, setx, delx, "") - check(x, size(h + '4Pi')) + check(x, size('4Pi')) # PyCObject # PyCapsule # XXX # rangeiterator - check(iter(xrange(1)), size(h + '4l')) + check(iter(xrange(1)), size('4l')) # reverse - check(reversed(''), size(h + 'PP')) + check(reversed(''), size('PP')) # set # frozenset PySet_MINSIZE = 8 samples = [[], range(10), range(50)] - s = size(h + '3P2P' + PySet_MINSIZE*'lP' + 'lP') + s = size('3P2P' + PySet_MINSIZE*'lP' + 'lP') for sample in samples: minused = len(sample) if minused == 0: tmp = 1 @@ -732,23 +699,24 @@ check(set(sample), s + newsize*struct.calcsize('lP')) check(frozenset(sample), s + newsize*struct.calcsize('lP')) # setiterator - check(iter(set()), size(h + 'P3P')) + check(iter(set()), size('P3P')) # slice - check(slice(1), size(h + '3P')) + check(slice(1), size('3P')) # str - check('', struct.calcsize(vh + 'li') + 1) - check('abc', struct.calcsize(vh + 'li') + 1 + 3*self.c) + vh = test.test_support._vheader + check('', struct.calcsize(vh + 'lic')) + check('abc', struct.calcsize(vh + 'lic') + 3) # super - check(super(int), size(h + '3P')) + check(super(int), size('3P')) # tuple - check((), size(vh)) - check((1,2,3), size(vh) + 3*self.P) + check((), vsize('')) + check((1,2,3), vsize('') + 3*self.P) # tupleiterator - check(iter(()), size(h + 'lP')) + check(iter(()), size('lP')) # type # (PyTypeObject + PyNumberMethods + PyMappingMethods + # PySequenceMethods + PyBufferProcs) - s = size(vh + 'P2P15Pl4PP9PP11PI') + size('41P 10P 3P 6P') + s = vsize('P2P15Pl4PP9PP11PI') + struct.calcsize('41P 10P 3P 6P') class newstyleclass(object): pass check(newstyleclass, s) @@ -763,41 +731,40 @@ # we need to test for both sizes, because we don't know if the string # has been cached for s in samples: - check(s, size(h + 'PPlP') + usize * (len(s) + 1)) + check(s, size('PPlP') + usize * (len(s) + 1)) # weakref import weakref - check(weakref.ref(int), size(h + '2Pl2P')) + check(weakref.ref(int), size('2Pl2P')) # weakproxy # XXX # weakcallableproxy - check(weakref.proxy(int), size(h + '2Pl2P')) + check(weakref.proxy(int), size('2Pl2P')) # xrange - check(xrange(1), size(h + '3l')) - check(xrange(66000), size(h + '3l')) + check(xrange(1), size('3l')) + check(xrange(66000), size('3l')) def test_pythontypes(self): # check all types defined in Python/ - h = self.header - vh = self.vheader - size = self.calcsize + size = test.test_support.calcobjsize + vsize = test.test_support.calcvobjsize check = self.check_sizeof # _ast.AST import _ast - check(_ast.AST(), size(h + '')) + check(_ast.AST(), size('')) # imp.NullImporter import imp - check(imp.NullImporter(self.file.name), size(h + '')) + check(imp.NullImporter(self.file.name), size('')) try: raise TypeError except TypeError: tb = sys.exc_info()[2] # traceback if tb != None: - check(tb, size(h + '2P2i')) + check(tb, size('2P2i')) # symtable entry # XXX # sys.flags - check(sys.flags, size(vh) + self.P * len(sys.flags)) + check(sys.flags, vsize('') + self.P * len(sys.flags)) def test_main(): diff --git a/lib-python/2.7/test/test_sys_settrace.py b/lib-python/2.7/test/test_sys_settrace.py --- a/lib-python/2.7/test/test_sys_settrace.py +++ b/lib-python/2.7/test/test_sys_settrace.py @@ -417,7 +417,7 @@ except ValueError: pass else: - self.fail("exception not thrown!") + self.fail("exception not raised!") except RuntimeError: self.fail("recursion counter not reset") @@ -670,6 +670,14 @@ no_jump_to_non_integers.jump = (2, "Spam") no_jump_to_non_integers.output = [True] +def jump_across_with(output): + with open(test_support.TESTFN, "wb") as fp: + pass + with open(test_support.TESTFN, "wb") as fp: + pass +jump_across_with.jump = (1, 3) +jump_across_with.output = [] + # This verifies that you can't set f_lineno via _getframe or similar # trickery. def no_jump_without_trace_function(): @@ -739,6 +747,9 @@ self.run_test(no_jump_to_non_integers) def test_19_no_jump_without_trace_function(self): no_jump_without_trace_function() + def test_jump_across_with(self): + self.addCleanup(test_support.unlink, test_support.TESTFN) + self.run_test(jump_across_with) def test_20_large_function(self): d = {} diff --git a/lib-python/2.7/test/test_sysconfig.py b/lib-python/2.7/test/test_sysconfig.py --- a/lib-python/2.7/test/test_sysconfig.py +++ b/lib-python/2.7/test/test_sysconfig.py @@ -14,6 +14,7 @@ get_path, get_path_names, _INSTALL_SCHEMES, _get_default_scheme, _expand_vars, get_scheme_names, get_config_var) +import _osx_support class TestSysConfig(unittest.TestCase): @@ -137,6 +138,7 @@ ('Darwin Kernel Version 8.11.1: ' 'Wed Oct 10 18:23:28 PDT 2007; ' 'root:xnu-792.25.20~1/RELEASE_I386'), 'PowerPC')) + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3' get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g ' @@ -156,6 +158,7 @@ ('Darwin Kernel Version 8.11.1: ' 'Wed Oct 10 18:23:28 PDT 2007; ' 'root:xnu-792.25.20~1/RELEASE_I386'), 'i386')) + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3' get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g ' @@ -171,6 +174,7 @@ sys.maxint = maxint # macbook with fat binaries (fat, universal or fat64) + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.4' get_config_vars()['CFLAGS'] = ('-arch ppc -arch i386 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' @@ -179,6 +183,7 @@ self.assertEqual(get_platform(), 'macosx-10.4-fat') + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch i386 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' @@ -186,18 +191,21 @@ self.assertEqual(get_platform(), 'macosx-10.4-intel') + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc -arch i386 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' '-dynamic -DNDEBUG -g -O3') self.assertEqual(get_platform(), 'macosx-10.4-fat3') + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch ppc64 -arch x86_64 -arch ppc -arch i386 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' '-dynamic -DNDEBUG -g -O3') self.assertEqual(get_platform(), 'macosx-10.4-universal') + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc64 -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' @@ -206,6 +214,7 @@ self.assertEqual(get_platform(), 'macosx-10.4-fat64') for arch in ('ppc', 'i386', 'x86_64', 'ppc64'): + _osx_support._remove_original_values(get_config_vars()) get_config_vars()['CFLAGS'] = ('-arch %s -isysroot ' '/Developer/SDKs/MacOSX10.4u.sdk ' '-fno-strict-aliasing -fno-common ' diff --git a/lib-python/2.7/test/test_tarfile.py b/lib-python/2.7/test/test_tarfile.py --- a/lib-python/2.7/test/test_tarfile.py +++ b/lib-python/2.7/test/test_tarfile.py @@ -154,6 +154,9 @@ def test_fileobj_symlink2(self): self._test_fileobj_link("./ustar/linktest2/symtype", "ustar/linktest1/regtype") + def test_issue14160(self): + self._test_fileobj_link("symtype2", "ustar/regtype") + class CommonReadTest(ReadTest): @@ -294,26 +297,21 @@ def test_extract_hardlink(self): # Test hardlink extraction (e.g. bug #857297). - tar = tarfile.open(tarname, errorlevel=1, encoding="iso8859-1") + with tarfile.open(tarname, errorlevel=1, encoding="iso8859-1") as tar: + tar.extract("ustar/regtype", TEMPDIR) + self.addCleanup(os.remove, os.path.join(TEMPDIR, "ustar/regtype")) - tar.extract("ustar/regtype", TEMPDIR) - try: tar.extract("ustar/lnktype", TEMPDIR) - except EnvironmentError, e: - if e.errno == errno.ENOENT: - self.fail("hardlink not extracted properly") + self.addCleanup(os.remove, os.path.join(TEMPDIR, "ustar/lnktype")) + with open(os.path.join(TEMPDIR, "ustar/lnktype"), "rb") as f: + data = f.read() + self.assertEqual(md5sum(data), md5_regtype) - data = open(os.path.join(TEMPDIR, "ustar/lnktype"), "rb").read() - self.assertEqual(md5sum(data), md5_regtype) - - try: tar.extract("ustar/symtype", TEMPDIR) - except EnvironmentError, e: - if e.errno == errno.ENOENT: - self.fail("symlink not extracted properly") - - data = open(os.path.join(TEMPDIR, "ustar/symtype"), "rb").read() - self.assertEqual(md5sum(data), md5_regtype) + self.addCleanup(os.remove, os.path.join(TEMPDIR, "ustar/symtype")) + with open(os.path.join(TEMPDIR, "ustar/symtype"), "rb") as f: + data = f.read() + self.assertEqual(md5sum(data), md5_regtype) def test_extractall(self): # Test if extractall() correctly restores directory permissions @@ -855,7 +853,7 @@ tar = tarfile.open(tmpname, "r") for t in tar: - self.assert_(t.name == "." or t.name.startswith("./")) + self.assertTrue(t.name == "." or t.name.startswith("./")) tar.close() finally: os.chdir(cwd) diff --git a/lib-python/2.7/test/test_tcl.py b/lib-python/2.7/test/test_tcl.py --- a/lib-python/2.7/test/test_tcl.py +++ b/lib-python/2.7/test/test_tcl.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import unittest +import sys import os from test import test_support @@ -151,6 +152,27 @@ # exit code must be zero self.assertEqual(f.close(), None) + def test_passing_values(self): + def passValue(value): + return self.interp.call('set', '_', value) + self.assertEqual(passValue(True), True) + self.assertEqual(passValue(False), False) + self.assertEqual(passValue('string'), 'string') + self.assertEqual(passValue('string\u20ac'), 'string\u20ac') + self.assertEqual(passValue(u'string'), u'string') + self.assertEqual(passValue(u'string\u20ac'), u'string\u20ac') + for i in (0, 1, -1, int(2**31-1), int(-2**31)): + self.assertEqual(passValue(i), i) + for f in (0.0, 1.0, -1.0, 1//3, 1/3.0, + sys.float_info.min, sys.float_info.max, + -sys.float_info.min, -sys.float_info.max): + self.assertEqual(passValue(f), f) + for f in float('nan'), float('inf'), -float('inf'): + if f != f: # NaN + self.assertNotEqual(passValue(f), f) + else: + self.assertEqual(passValue(f), f) + self.assertEqual(passValue((1, '2', (3.4,))), (1, '2', (3.4,))) def test_main(): diff --git a/lib-python/2.7/test/test_telnetlib.py b/lib-python/2.7/test/test_telnetlib.py --- a/lib-python/2.7/test/test_telnetlib.py +++ b/lib-python/2.7/test/test_telnetlib.py @@ -3,6 +3,7 @@ import time import Queue +import unittest from unittest import TestCase from test import test_support threading = test_support.import_module('threading') @@ -135,6 +136,28 @@ self.assertEqual(data, want[0]) self.assertEqual(telnet.read_all(), 'not seen') + def test_read_until_with_poll(self): + """Use select.poll() to implement telnet.read_until().""" + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + if not telnet._has_poll: + raise unittest.SkipTest('select.poll() is required') + telnet._has_poll = True + self.dataq.join() + data = telnet.read_until('match') + self.assertEqual(data, ''.join(want[:-2])) + + def test_read_until_with_select(self): + """Use select.select() to implement telnet.read_until().""" + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + telnet._has_poll = False + self.dataq.join() + data = telnet.read_until('match') + self.assertEqual(data, ''.join(want[:-2])) + def test_read_all_A(self): """ read_all() @@ -357,8 +380,75 @@ self.assertEqual('', telnet.read_sb_data()) nego.sb_getter = None # break the nego => telnet cycle + +class ExpectTests(TestCase): + def setUp(self): + self.evt = threading.Event() + self.dataq = Queue.Queue() + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.settimeout(10) + self.port = test_support.bind_port(self.sock) + self.thread = threading.Thread(target=server, args=(self.evt,self.sock, + self.dataq)) + self.thread.start() + self.evt.wait() + + def tearDown(self): + self.thread.join() + + # use a similar approach to testing timeouts as test_timeout.py + # these will never pass 100% but make the fuzz big enough that it is rare + block_long = 0.6 + block_short = 0.3 + def test_expect_A(self): + """ + expect(expected, [timeout]) + Read until the expected string has been seen, or a timeout is + hit (default is no timeout); may block. + """ + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + self.dataq.join() + (_,_,data) = telnet.expect(['match']) + self.assertEqual(data, ''.join(want[:-2])) + + def test_expect_B(self): + # test the timeout - it does NOT raise socket.timeout + want = ['hello', self.block_long, 'not seen', EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + self.dataq.join() + (_,_,data) = telnet.expect(['not seen'], self.block_short) + self.assertEqual(data, want[0]) + self.assertEqual(telnet.read_all(), 'not seen') + + def test_expect_with_poll(self): + """Use select.poll() to implement telnet.expect().""" + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + if not telnet._has_poll: + raise unittest.SkipTest('select.poll() is required') + telnet._has_poll = True + self.dataq.join() + (_,_,data) = telnet.expect(['match']) + self.assertEqual(data, ''.join(want[:-2])) + + def test_expect_with_select(self): + """Use select.select() to implement telnet.expect().""" + want = ['x' * 10, 'match', 'y' * 10, EOF_sigil] + self.dataq.put(want) + telnet = telnetlib.Telnet(HOST, self.port) + telnet._has_poll = False + self.dataq.join() + (_,_,data) = telnet.expect(['match']) + self.assertEqual(data, ''.join(want[:-2])) + + def test_main(verbose=None): - test_support.run_unittest(GeneralTests, ReadTests, OptionTests) + test_support.run_unittest(GeneralTests, ReadTests, OptionTests, + ExpectTests) if __name__ == '__main__': test_main() diff --git a/lib-python/2.7/test/test_tempfile.py b/lib-python/2.7/test/test_tempfile.py --- a/lib-python/2.7/test/test_tempfile.py +++ b/lib-python/2.7/test/test_tempfile.py @@ -1,13 +1,16 @@ # tempfile.py unit tests. import tempfile +import errno +import io import os import signal +import shutil import sys import re import warnings import unittest -from test import test_support +from test import test_support as support warnings.filterwarnings("ignore", category=RuntimeWarning, @@ -177,7 +180,7 @@ # _candidate_tempdir_list contains the expected directories # Make sure the interesting environment variables are all set. - with test_support.EnvironmentVarGuard() as env: + with support.EnvironmentVarGuard() as env: for envname in 'TMPDIR', 'TEMP', 'TMP': dirname = os.getenv(envname) if not dirname: @@ -202,8 +205,51 @@ test_classes.append(test__candidate_tempdir_list) +# We test _get_default_tempdir some more by testing gettempdir. -# We test _get_default_tempdir by testing gettempdir. +class TestGetDefaultTempdir(TC): + """Test _get_default_tempdir().""" + + def test_no_files_left_behind(self): + # use a private empty directory + our_temp_directory = tempfile.mkdtemp() + try: + # force _get_default_tempdir() to consider our empty directory + def our_candidate_list(): + return [our_temp_directory] + + with support.swap_attr(tempfile, "_candidate_tempdir_list", + our_candidate_list): + # verify our directory is empty after _get_default_tempdir() + tempfile._get_default_tempdir() + self.assertEqual(os.listdir(our_temp_directory), []) + + def raise_OSError(*args, **kwargs): + raise OSError(-1) + + with support.swap_attr(io, "open", raise_OSError): + # test again with failing io.open() + with self.assertRaises(IOError) as cm: + tempfile._get_default_tempdir() + self.assertEqual(cm.exception.errno, errno.ENOENT) + self.assertEqual(os.listdir(our_temp_directory), []) + + open = io.open + def bad_writer(*args, **kwargs): + fp = open(*args, **kwargs) + fp.write = raise_OSError + return fp + + with support.swap_attr(io, "open", bad_writer): + # test again with failing write() + with self.assertRaises(IOError) as cm: + tempfile._get_default_tempdir() + self.assertEqual(cm.exception.errno, errno.ENOENT) + self.assertEqual(os.listdir(our_temp_directory), []) + finally: + shutil.rmtree(our_temp_directory) + +test_classes.append(TestGetDefaultTempdir) class test__get_candidate_names(TC): @@ -299,7 +345,7 @@ if not has_spawnl: return # ugh, can't use SkipTest. - if test_support.verbose: + if support.verbose: v="v" else: v="q" @@ -738,6 +784,17 @@ f.write(b'x') self.assertTrue(f._rolled) + def test_xreadlines(self): + f = self.do_create(max_size=20) + f.write(b'abc\n' * 5) + f.seek(0) + self.assertFalse(f._rolled) + self.assertEqual(list(f.xreadlines()), [b'abc\n'] * 5) + f.write(b'x\ny') + self.assertTrue(f._rolled) + f.seek(0) + self.assertEqual(list(f.xreadlines()), [b'abc\n'] * 5 + [b'x\n', b'y']) + def test_sparse(self): # A SpooledTemporaryFile that is written late in the file will extend # when that occurs @@ -793,6 +850,26 @@ seek(0, 0) self.assertTrue(read(70) == 'a'*35 + 'b'*35) + def test_properties(self): + f = tempfile.SpooledTemporaryFile(max_size=10) + f.write(b'x' * 10) + self.assertFalse(f._rolled) + self.assertEqual(f.mode, 'w+b') + self.assertIsNone(f.name) + with self.assertRaises(AttributeError): + f.newlines + with self.assertRaises(AttributeError): + f.encoding + + f.write(b'x') + self.assertTrue(f._rolled) + self.assertEqual(f.mode, 'w+b') + self.assertIsNotNone(f.name) + with self.assertRaises(AttributeError): + f.newlines + with self.assertRaises(AttributeError): + f.encoding + def test_context_manager_before_rollover(self): # A SpooledTemporaryFile can be used as a context manager with tempfile.SpooledTemporaryFile(max_size=1) as f: @@ -882,7 +959,7 @@ test_classes.append(test_TemporaryFile) def test_main(): - test_support.run_unittest(*test_classes) + support.run_unittest(*test_classes) if __name__ == "__main__": test_main() diff --git a/lib-python/2.7/test/test_textwrap.py b/lib-python/2.7/test/test_textwrap.py --- a/lib-python/2.7/test/test_textwrap.py +++ b/lib-python/2.7/test/test_textwrap.py @@ -66,6 +66,15 @@ "I'm glad to hear it!"]) self.check_wrap(text, 80, [text]) + def test_empty_string(self): + # Check that wrapping the empty string returns an empty list. + self.check_wrap("", 6, []) + self.check_wrap("", 6, [], drop_whitespace=False) + + def test_empty_string_with_initial_indent(self): + # Check that the empty string is not indented. + self.check_wrap("", 6, [], initial_indent="++") + self.check_wrap("", 6, [], initial_indent="++", drop_whitespace=False) def test_whitespace(self): # Whitespace munging and end-of-sentence detection @@ -323,7 +332,32 @@ ["blah", " ", "(ding", " ", "dong),", " ", "wubba"]) - def test_initial_whitespace(self): + def test_drop_whitespace_false(self): + # Check that drop_whitespace=False preserves whitespace. + # SF patch #1581073 + text = " This is a sentence with much whitespace." + self.check_wrap(text, 10, + [" This is a", " ", "sentence ", + "with ", "much white", "space."], + drop_whitespace=False) + + def test_drop_whitespace_false_whitespace_only(self): + # Check that drop_whitespace=False preserves a whitespace-only string. + self.check_wrap(" ", 6, [" "], drop_whitespace=False) + + def test_drop_whitespace_false_whitespace_only_with_indent(self): + # Check that a whitespace-only string gets indented (when + # drop_whitespace is False). + self.check_wrap(" ", 6, [" "], drop_whitespace=False, + initial_indent=" ") + + def test_drop_whitespace_whitespace_only(self): + # Check drop_whitespace on a whitespace-only string. + self.check_wrap(" ", 6, []) + + def test_drop_whitespace_leading_whitespace(self): + # Check that drop_whitespace does not drop leading whitespace (if + # followed by non-whitespace). # SF bug #622849 reported inconsistent handling of leading # whitespace; let's test that a bit, shall we? text = " This is a sentence with leading whitespace." @@ -332,13 +366,27 @@ self.check_wrap(text, 30, [" This is a sentence with", "leading whitespace."]) - def test_no_drop_whitespace(self): - # SF patch #1581073 - text = " This is a sentence with much whitespace." - self.check_wrap(text, 10, - [" This is a", " ", "sentence ", - "with ", "much white", "space."], + def test_drop_whitespace_whitespace_line(self): + # Check that drop_whitespace skips the whole line if a non-leading + # line consists only of whitespace. + text = "abcd efgh" + # Include the result for drop_whitespace=False for comparison. + self.check_wrap(text, 6, ["abcd", " ", "efgh"], drop_whitespace=False) + self.check_wrap(text, 6, ["abcd", "efgh"]) + + def test_drop_whitespace_whitespace_only_with_indent(self): + # Check that initial_indent is not applied to a whitespace-only + # string. This checks a special case of the fact that dropping + # whitespace occurs before indenting. + self.check_wrap(" ", 6, [], initial_indent="++") + + def test_drop_whitespace_whitespace_indent(self): + # Check that drop_whitespace does not drop whitespace indents. + # This checks a special case of the fact that dropping whitespace + # occurs before indenting. + self.check_wrap("abcd efgh", 6, [" abcd", " efgh"], + initial_indent=" ", subsequent_indent=" ") if test_support.have_unicode: def test_unicode(self): diff --git a/lib-python/2.7/test/test_thread.py b/lib-python/2.7/test/test_thread.py --- a/lib-python/2.7/test/test_thread.py +++ b/lib-python/2.7/test/test_thread.py @@ -130,6 +130,29 @@ time.sleep(0.01) self.assertEqual(thread._count(), orig) + def test_save_exception_state_on_error(self): + # See issue #14474 + def task(): + started.release() + raise SyntaxError + def mywrite(self, *args): + try: + raise ValueError + except ValueError: + pass + real_write(self, *args) + c = thread._count() + started = thread.allocate_lock() + with test_support.captured_output("stderr") as stderr: + real_write = stderr.write + stderr.write = mywrite + started.acquire() + thread.start_new_thread(task, ()) + started.acquire() + while thread._count() > c: + time.sleep(0.01) + self.assertIn("Traceback", stderr.getvalue()) + class Barrier: def __init__(self, num_threads): diff --git a/lib-python/2.7/test/test_threading.py b/lib-python/2.7/test/test_threading.py --- a/lib-python/2.7/test/test_threading.py +++ b/lib-python/2.7/test/test_threading.py @@ -2,6 +2,8 @@ import test.test_support from test.test_support import verbose +from test.script_helper import assert_python_ok + import random import re import sys @@ -414,6 +416,33 @@ msg=('%d references still around' % sys.getrefcount(weak_raising_cyclic_object()))) + @unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()') + def test_dummy_thread_after_fork(self): + # Issue #14308: a dummy thread in the active list doesn't mess up + # the after-fork mechanism. + code = """if 1: + import thread, threading, os, time + + def background_thread(evt): + # Creates and registers the _DummyThread instance + threading.current_thread() + evt.set() + time.sleep(10) + + evt = threading.Event() + thread.start_new_thread(background_thread, (evt,)) + evt.wait() + assert threading.active_count() == 2, threading.active_count() + if os.fork() == 0: + assert threading.active_count() == 1, threading.active_count() + os._exit(0) + else: + os.wait() + """ + _, out, err = assert_python_ok("-c", code) + self.assertEqual(out, '') + self.assertEqual(err, '') + class ThreadJoinOnShutdown(BaseTestCase): diff --git a/lib-python/2.7/test/test_time.py b/lib-python/2.7/test/test_time.py --- a/lib-python/2.7/test/test_time.py +++ b/lib-python/2.7/test/test_time.py @@ -106,7 +106,7 @@ def test_strptime(self): # Should be able to go round-trip from strftime to strptime without - # throwing an exception. + # raising an exception. tt = time.gmtime(self.t) for directive in ('a', 'A', 'b', 'B', 'c', 'd', 'H', 'I', 'j', 'm', 'M', 'p', 'S', diff --git a/lib-python/2.7/test/test_tokenize.py b/lib-python/2.7/test/test_tokenize.py --- a/lib-python/2.7/test/test_tokenize.py +++ b/lib-python/2.7/test/test_tokenize.py @@ -278,6 +278,31 @@ OP '+' (1, 32) (1, 33) STRING 'UR"ABC"' (1, 34) (1, 41) + >>> dump_tokens("b'abc' + B'abc'") + STRING "b'abc'" (1, 0) (1, 6) + OP '+' (1, 7) (1, 8) + STRING "B'abc'" (1, 9) (1, 15) + >>> dump_tokens('b"abc" + B"abc"') + STRING 'b"abc"' (1, 0) (1, 6) + OP '+' (1, 7) (1, 8) + STRING 'B"abc"' (1, 9) (1, 15) + >>> dump_tokens("br'abc' + bR'abc' + Br'abc' + BR'abc'") + STRING "br'abc'" (1, 0) (1, 7) + OP '+' (1, 8) (1, 9) + STRING "bR'abc'" (1, 10) (1, 17) + OP '+' (1, 18) (1, 19) + STRING "Br'abc'" (1, 20) (1, 27) + OP '+' (1, 28) (1, 29) + STRING "BR'abc'" (1, 30) (1, 37) + >>> dump_tokens('br"abc" + bR"abc" + Br"abc" + BR"abc"') + STRING 'br"abc"' (1, 0) (1, 7) + OP '+' (1, 8) (1, 9) + STRING 'bR"abc"' (1, 10) (1, 17) + OP '+' (1, 18) (1, 19) + STRING 'Br"abc"' (1, 20) (1, 27) + OP '+' (1, 28) (1, 29) + STRING 'BR"abc"' (1, 30) (1, 37) + Operators >>> dump_tokens("def d22(a, b, c=2, d=2, *k): pass") @@ -525,6 +550,10 @@ NAME 'pass' (3, 9) (3, 13) DEDENT '' (4, 0) (4, 0) DEDENT '' (4, 0) (4, 0) + +Pathological whitespace (http://bugs.python.org/issue16152) + >>> dump_tokens("@ ") + OP '@' (1, 0) (1, 1) """ diff --git a/lib-python/2.7/test/test_tools.py b/lib-python/2.7/test/test_tools.py --- a/lib-python/2.7/test/test_tools.py +++ b/lib-python/2.7/test/test_tools.py @@ -5,22 +5,28 @@ """ import os +import sys import unittest +import shutil +import subprocess import sysconfig +import tempfile +import textwrap from test import test_support -from test.script_helper import assert_python_ok +from test.script_helper import assert_python_ok, temp_dir if not sysconfig.is_python_build(): # XXX some installers do contain the tools, should we detect that # and run the tests in that case too? raise unittest.SkipTest('test irrelevant for an installed Python') -srcdir = sysconfig.get_config_var('projectbase') -basepath = os.path.join(os.getcwd(), srcdir, 'Tools') +basepath = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + 'Tools') +scriptsdir = os.path.join(basepath, 'scripts') class ReindentTests(unittest.TestCase): - script = os.path.join(basepath, 'scripts', 'reindent.py') + script = os.path.join(scriptsdir, 'reindent.py') def test_noargs(self): assert_python_ok(self.script) @@ -31,8 +37,331 @@ self.assertGreater(err, b'') +class PindentTests(unittest.TestCase): + script = os.path.join(scriptsdir, 'pindent.py') + + def assertFileEqual(self, fn1, fn2): + with open(fn1) as f1, open(fn2) as f2: + self.assertEqual(f1.readlines(), f2.readlines()) + + def pindent(self, source, *args): + proc = subprocess.Popen( + (sys.executable, self.script) + args, + stdin=subprocess.PIPE, stdout=subprocess.PIPE, + universal_newlines=True) + out, err = proc.communicate(source) + self.assertIsNone(err) + return out + + def lstriplines(self, data): + return '\n'.join(line.lstrip() for line in data.splitlines()) + '\n' + + def test_selftest(self): + self.maxDiff = None + with temp_dir() as directory: + data_path = os.path.join(directory, '_test.py') + with open(self.script) as f: + closed = f.read() + with open(data_path, 'w') as f: + f.write(closed) + + rc, out, err = assert_python_ok(self.script, '-d', data_path) + self.assertEqual(out, b'') + self.assertEqual(err, b'') + backup = data_path + '~' + self.assertTrue(os.path.exists(backup)) + with open(backup) as f: + self.assertEqual(f.read(), closed) + with open(data_path) as f: + clean = f.read() + compile(clean, '_test.py', 'exec') + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + + rc, out, err = assert_python_ok(self.script, '-c', data_path) + self.assertEqual(out, b'') + self.assertEqual(err, b'') + with open(backup) as f: + self.assertEqual(f.read(), clean) + with open(data_path) as f: + self.assertEqual(f.read(), closed) + + broken = self.lstriplines(closed) + with open(data_path, 'w') as f: + f.write(broken) + rc, out, err = assert_python_ok(self.script, '-r', data_path) + self.assertEqual(out, b'') + self.assertEqual(err, b'') + with open(backup) as f: + self.assertEqual(f.read(), broken) + with open(data_path) as f: + indented = f.read() + compile(indented, '_test.py', 'exec') + self.assertEqual(self.pindent(broken, '-r'), indented) + + def pindent_test(self, clean, closed): + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + broken = self.lstriplines(closed) + self.assertEqual(self.pindent(broken, '-r', '-e', '-s', '4'), closed) + + def test_statements(self): + clean = textwrap.dedent("""\ + if a: + pass + + if a: + pass + else: + pass + + if a: + pass + elif: + pass + else: + pass + + while a: + break + + while a: + break + else: + pass + + for i in a: + break + + for i in a: + break + else: + pass + + try: + pass + finally: + pass + + try: + pass + except TypeError: + pass + except ValueError: + pass + else: + pass + + try: + pass + except TypeError: + pass + except ValueError: + pass + finally: + pass + + with a: + pass + + class A: + pass + + def f(): + pass + """) + + closed = textwrap.dedent("""\ + if a: + pass + # end if + + if a: + pass + else: + pass + # end if + + if a: + pass + elif: + pass + else: + pass + # end if + + while a: + break + # end while + + while a: + break + else: + pass + # end while + + for i in a: + break + # end for + + for i in a: + break + else: + pass + # end for + + try: + pass + finally: + pass + # end try + + try: + pass + except TypeError: + pass + except ValueError: + pass + else: + pass + # end try + + try: + pass + except TypeError: + pass + except ValueError: + pass + finally: + pass + # end try + + with a: + pass + # end with + + class A: + pass + # end class A + + def f(): + pass + # end def f + """) + self.pindent_test(clean, closed) + + def test_multilevel(self): + clean = textwrap.dedent("""\ + def foobar(a, b): + if a == b: + a = a+1 + elif a < b: + b = b-1 + if b > a: a = a-1 + else: + print 'oops!' + """) + closed = textwrap.dedent("""\ + def foobar(a, b): + if a == b: + a = a+1 + elif a < b: + b = b-1 + if b > a: a = a-1 + # end if + else: + print 'oops!' + # end if + # end def foobar + """) + self.pindent_test(clean, closed) + + def test_preserve_indents(self): + clean = textwrap.dedent("""\ + if a: + if b: + pass + """) + closed = textwrap.dedent("""\ + if a: + if b: + pass + # end if + # end if + """) + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + broken = self.lstriplines(closed) + self.assertEqual(self.pindent(broken, '-r', '-e', '-s', '9'), closed) + clean = textwrap.dedent("""\ + if a: + \tif b: + \t\tpass + """) + closed = textwrap.dedent("""\ + if a: + \tif b: + \t\tpass + \t# end if + # end if + """) + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + broken = self.lstriplines(closed) + self.assertEqual(self.pindent(broken, '-r'), closed) + + def test_escaped_newline(self): + clean = textwrap.dedent("""\ + class\\ + \\ + A: + def\ + \\ + f: + pass + """) + closed = textwrap.dedent("""\ + class\\ + \\ + A: + def\ + \\ + f: + pass + # end def f + # end class A + """) + self.assertEqual(self.pindent(clean, '-c'), closed) + self.assertEqual(self.pindent(closed, '-d'), clean) + + def test_empty_line(self): + clean = textwrap.dedent("""\ + if a: + + pass + """) + closed = textwrap.dedent("""\ + if a: + + pass + # end if + """) + self.pindent_test(clean, closed) + + def test_oneline(self): + clean = textwrap.dedent("""\ + if a: pass + """) + closed = textwrap.dedent("""\ + if a: pass + # end if + """) + self.pindent_test(clean, closed) + + def test_main(): - test_support.run_unittest(ReindentTests) + test_support.run_unittest(*[obj for obj in globals().values() + if isinstance(obj, type)]) if __name__ == '__main__': diff --git a/lib-python/2.7/test/test_ucn.py b/lib-python/2.7/test/test_ucn.py --- a/lib-python/2.7/test/test_ucn.py +++ b/lib-python/2.7/test/test_ucn.py @@ -8,6 +8,8 @@ """#" import unittest +import sys +import _testcapi from test import test_support @@ -137,6 +139,27 @@ unicode, "\\NSPACE", 'unicode-escape', 'strict' ) + @unittest.skipUnless(_testcapi.INT_MAX < _testcapi.PY_SSIZE_T_MAX, + "needs UINT_MAX < SIZE_MAX") + @unittest.skipUnless(_testcapi.UINT_MAX < sys.maxint, + "needs UINT_MAX < sys.maxint") + @test_support.bigmemtest(minsize=_testcapi.UINT_MAX + 1, + memuse=2 + 4 // len(u'\U00010000')) + def test_issue16335(self, size): + func = self.test_issue16335 + if size < func.minsize: + raise unittest.SkipTest("not enough memory: %.1fG minimum needed" % + (func.minsize * func.memuse / float(1024**3),)) + # very very long bogus character name + x = b'\\N{SPACE' + b'x' * int(_testcapi.UINT_MAX + 1) + b'}' + self.assertEqual(len(x), len(b'\\N{SPACE}') + + (_testcapi.UINT_MAX + 1)) + self.assertRaisesRegexp(UnicodeError, + 'unknown Unicode character name', + x.decode, 'unicode-escape' + ) + + def test_main(): test_support.run_unittest(UnicodeNamesTest) diff --git a/lib-python/2.7/test/test_unicode.py b/lib-python/2.7/test/test_unicode.py --- a/lib-python/2.7/test/test_unicode.py +++ b/lib-python/2.7/test/test_unicode.py @@ -644,6 +644,18 @@ return u'\u1234' self.assertEqual('%s' % Wrapper(), u'\u1234') + @test_support.cpython_only + def test_formatting_huge_precision(self): + from _testcapi import INT_MAX + format_string = u"%.{}f".format(INT_MAX + 1) + with self.assertRaises(ValueError): + result = format_string % 2.34 + + def test_formatting_huge_width(self): + format_string = u"%{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format_string % 2.34 + def test_startswith_endswith_errors(self): for meth in (u'foo'.startswith, u'foo'.endswith): with self.assertRaises(UnicodeDecodeError): @@ -1556,6 +1568,21 @@ # will fail self.assertRaises(UnicodeEncodeError, "foo{0}".format, u'\u1000bar') + def test_format_huge_precision(self): + format_string = u".{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format(2.34, format_string) + + def test_format_huge_width(self): + format_string = u"{}f".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format(2.34, format_string) + + def test_format_huge_item_number(self): + format_string = u"{{{}:.6f}}".format(sys.maxsize + 1) + with self.assertRaises(ValueError): + result = format_string.format(2.34) + def test_format_auto_numbering(self): class C: def __init__(self, x=100): diff --git a/lib-python/2.7/test/test_urllib.py b/lib-python/2.7/test/test_urllib.py --- a/lib-python/2.7/test/test_urllib.py +++ b/lib-python/2.7/test/test_urllib.py @@ -222,6 +222,27 @@ finally: self.unfakehttp() + def test_missing_localfile(self): + self.assertRaises(IOError, urllib.urlopen, + 'file://localhost/a/missing/file.py') + fd, tmp_file = tempfile.mkstemp() + tmp_fileurl = 'file://localhost/' + tmp_file.replace(os.path.sep, '/') + try: + self.assertTrue(os.path.exists(tmp_file)) + fp = urllib.urlopen(tmp_fileurl) + finally: + os.close(fd) + fp.close() + os.unlink(tmp_file) + + self.assertFalse(os.path.exists(tmp_file)) + self.assertRaises(IOError, urllib.urlopen, tmp_fileurl) + + def test_ftp_nonexisting(self): + self.assertRaises(IOError, urllib.urlopen, + 'ftp://localhost/not/existing/file.py') + + def test_userpass_inurl(self): self.fakehttp('Hello!') try: diff --git a/lib-python/2.7/test/test_urllib2.py b/lib-python/2.7/test/test_urllib2.py --- a/lib-python/2.7/test/test_urllib2.py +++ b/lib-python/2.7/test/test_urllib2.py @@ -1106,12 +1106,30 @@ self._test_basic_auth(opener, auth_handler, "Authorization", realm, http_handler, password_manager, "http://acme.example.com/protected", - "http://acme.example.com/protected", - ) + "http://acme.example.com/protected" + ) def test_basic_auth_with_single_quoted_realm(self): self.test_basic_auth(quote_char="'") + def test_basic_auth_with_unquoted_realm(self): + opener = OpenerDirector() + password_manager = MockPasswordManager() + auth_handler = urllib2.HTTPBasicAuthHandler(password_manager) + realm = "ACME Widget Store" + http_handler = MockHTTPHandler( + 401, 'WWW-Authenticate: Basic realm=%s\r\n\r\n' % realm) + opener.add_handler(auth_handler) + opener.add_handler(http_handler) + msg = "Basic Auth Realm was unquoted" + with test_support.check_warnings((msg, UserWarning)): + self._test_basic_auth(opener, auth_handler, "Authorization", + realm, http_handler, password_manager, + "http://acme.example.com/protected", + "http://acme.example.com/protected" + ) + + def test_proxy_basic_auth(self): opener = OpenerDirector() ph = urllib2.ProxyHandler(dict(http="proxy.example.com:3128")) @@ -1130,7 +1148,7 @@ ) def test_basic_and_digest_auth_handlers(self): - # HTTPDigestAuthHandler threw an exception if it couldn't handle a 40* + # HTTPDigestAuthHandler raised an exception if it couldn't handle a 40* # response (http://python.org/sf/1479302), where it should instead # return None to allow another handler (especially # HTTPBasicAuthHandler) to handle the response. @@ -1318,16 +1336,32 @@ req = Request(url) self.assertEqual(req.get_full_url(), url) -def test_HTTPError_interface(): - """ - Issue 13211 reveals that HTTPError didn't implement the URLError - interface even though HTTPError is a subclass of URLError. + def test_HTTPError_interface(self): + """ + Issue 13211 reveals that HTTPError didn't implement the URLError + interface even though HTTPError is a subclass of URLError. - >>> err = urllib2.HTTPError(msg='something bad happened', url=None, code=None, hdrs=None, fp=None) - >>> assert hasattr(err, 'reason') - >>> err.reason - 'something bad happened' - """ + >>> err = urllib2.HTTPError(msg='something bad happened', url=None, code=None, hdrs=None, fp=None) + >>> assert hasattr(err, 'reason') + >>> err.reason + 'something bad happened' + """ + + def test_HTTPError_interface_call(self): + """ + Issue 15701= - HTTPError interface has info method available from URLError. + """ + err = urllib2.HTTPError(msg='something bad happened', url=None, + code=None, hdrs='Content-Length:42', fp=None) + self.assertTrue(hasattr(err, 'reason')) + assert hasattr(err, 'reason') + assert hasattr(err, 'info') + assert callable(err.info) + try: + err.info() + except AttributeError: + self.fail("err.info() failed") + self.assertEqual(err.info(), "Content-Length:42") def test_main(verbose=None): from test import test_urllib2 diff --git a/lib-python/2.7/test/test_urllib2_localnet.py b/lib-python/2.7/test/test_urllib2_localnet.py --- a/lib-python/2.7/test/test_urllib2_localnet.py +++ b/lib-python/2.7/test/test_urllib2_localnet.py @@ -5,7 +5,9 @@ import BaseHTTPServer import unittest import hashlib + from test import test_support + mimetools = test_support.import_module('mimetools', deprecated=True) threading = test_support.import_module('threading') @@ -346,6 +348,12 @@ for transparent redirection have been written. """ + def setUp(self): + proxy_handler = urllib2.ProxyHandler({}) + opener = urllib2.build_opener(proxy_handler) + urllib2.install_opener(opener) + super(TestUrlopen, self).setUp() + def start_server(self, responses): handler = GetRequestHandler(responses) diff --git a/lib-python/2.7/test/test_urllib2net.py b/lib-python/2.7/test/test_urllib2net.py --- a/lib-python/2.7/test/test_urllib2net.py +++ b/lib-python/2.7/test/test_urllib2net.py @@ -157,12 +157,12 @@ ## self._test_urls(urls, self._extra_handlers()+[bauth, dauth]) def test_urlwithfrag(self): - urlwith_frag = "http://docs.python.org/glossary.html#glossary" + urlwith_frag = "http://docs.python.org/2/glossary.html#glossary" with test_support.transient_internet(urlwith_frag): req = urllib2.Request(urlwith_frag) res = urllib2.urlopen(req) self.assertEqual(res.geturl(), - "http://docs.python.org/glossary.html#glossary") + "http://docs.python.org/2/glossary.html#glossary") def test_fileno(self): req = urllib2.Request("http://www.python.org") diff --git a/lib-python/2.7/test/test_urlparse.py b/lib-python/2.7/test/test_urlparse.py --- a/lib-python/2.7/test/test_urlparse.py +++ b/lib-python/2.7/test/test_urlparse.py @@ -437,6 +437,51 @@ self.assertEqual(p.port, 80) self.assertEqual(p.geturl(), url) + # Verify an illegal port of value greater than 65535 is set as None + url = "http://www.python.org:65536" + p = urlparse.urlsplit(url) + self.assertEqual(p.port, None) + + def test_issue14072(self): + p1 = urlparse.urlsplit('tel:+31-641044153') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '+31-641044153') + + p2 = urlparse.urlsplit('tel:+31641044153') + self.assertEqual(p2.scheme, 'tel') + self.assertEqual(p2.path, '+31641044153') + + # Assert for urlparse + p1 = urlparse.urlparse('tel:+31-641044153') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '+31-641044153') + + p2 = urlparse.urlparse('tel:+31641044153') + self.assertEqual(p2.scheme, 'tel') + self.assertEqual(p2.path, '+31641044153') + + + def test_telurl_params(self): + p1 = urlparse.urlparse('tel:123-4;phone-context=+1-650-516') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '123-4') + self.assertEqual(p1.params, 'phone-context=+1-650-516') + + p1 = urlparse.urlparse('tel:+1-201-555-0123') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '+1-201-555-0123') + self.assertEqual(p1.params, '') + + p1 = urlparse.urlparse('tel:7042;phone-context=example.com') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '7042') + self.assertEqual(p1.params, 'phone-context=example.com') + + p1 = urlparse.urlparse('tel:863-1234;phone-context=+1-914-555') + self.assertEqual(p1.scheme, 'tel') + self.assertEqual(p1.path, '863-1234') + self.assertEqual(p1.params, 'phone-context=+1-914-555') + def test_attributes_bad_port(self): """Check handling of non-integer ports.""" @@ -493,6 +538,10 @@ ('s3','foo.com','/stuff','','','')) self.assertEqual(urlparse.urlparse("x-newscheme://foo.com/stuff"), ('x-newscheme','foo.com','/stuff','','','')) + self.assertEqual(urlparse.urlparse("x-newscheme://foo.com/stuff?query#fragment"), + ('x-newscheme','foo.com','/stuff','','query','fragment')) + self.assertEqual(urlparse.urlparse("x-newscheme://foo.com/stuff?query"), + ('x-newscheme','foo.com','/stuff','','query','')) def test_withoutscheme(self): # Test urlparse without scheme diff --git a/lib-python/2.7/test/test_uu.py b/lib-python/2.7/test/test_uu.py --- a/lib-python/2.7/test/test_uu.py +++ b/lib-python/2.7/test/test_uu.py @@ -48,7 +48,7 @@ out = cStringIO.StringIO() try: uu.decode(inp, out) - self.fail("No exception thrown") + self.fail("No exception raised") except uu.Error, e: self.assertEqual(str(e), "Truncated input file") @@ -57,7 +57,7 @@ out = cStringIO.StringIO() try: uu.decode(inp, out) - self.fail("No exception thrown") + self.fail("No exception raised") except uu.Error, e: self.assertEqual(str(e), "No valid begin line found in input file") diff --git a/lib-python/2.7/test/test_weakref.py b/lib-python/2.7/test/test_weakref.py --- a/lib-python/2.7/test/test_weakref.py +++ b/lib-python/2.7/test/test_weakref.py @@ -33,6 +33,27 @@ return C.method +class Object: + def __init__(self, arg): + self.arg = arg + def __repr__(self): + return "" % self.arg + def __eq__(self, other): + if isinstance(other, Object): + return self.arg == other.arg + return NotImplemented + def __ne__(self, other): + if isinstance(other, Object): + return self.arg != other.arg + return NotImplemented + def __hash__(self): + return hash(self.arg) + +class RefCycle: + def __init__(self): + self.cycle = self + + class TestBase(unittest.TestCase): def setUp(self): @@ -705,6 +726,75 @@ self.assertEqual(b(), None) self.assertEqual(l, [a, b]) + def test_equality(self): + # Alive weakrefs defer equality testing to their underlying object. + x = Object(1) + y = Object(1) + z = Object(2) + a = weakref.ref(x) + b = weakref.ref(y) + c = weakref.ref(z) + d = weakref.ref(x) + # Note how we directly test the operators here, to stress both + # __eq__ and __ne__. + self.assertTrue(a == b) + self.assertFalse(a != b) + self.assertFalse(a == c) + self.assertTrue(a != c) + self.assertTrue(a == d) + self.assertFalse(a != d) + del x, y, z + gc.collect() + for r in a, b, c: + # Sanity check + self.assertIs(r(), None) + # Dead weakrefs compare by identity: whether `a` and `d` are the + # same weakref object is an implementation detail, since they pointed + # to the same original object and didn't have a callback. + # (see issue #16453). + self.assertFalse(a == b) + self.assertTrue(a != b) + self.assertFalse(a == c) + self.assertTrue(a != c) + self.assertEqual(a == d, a is d) + self.assertEqual(a != d, a is not d) + + def test_hashing(self): + # Alive weakrefs hash the same as the underlying object + x = Object(42) + y = Object(42) + a = weakref.ref(x) + b = weakref.ref(y) + self.assertEqual(hash(a), hash(42)) + del x, y + gc.collect() + # Dead weakrefs: + # - retain their hash is they were hashed when alive; + # - otherwise, cannot be hashed. + self.assertEqual(hash(a), hash(42)) + self.assertRaises(TypeError, hash, b) + + def test_trashcan_16602(self): + # Issue #16602: when a weakref's target was part of a long + # deallocation chain, the trashcan mechanism could delay clearing + # of the weakref and make the target object visible from outside + # code even though its refcount had dropped to 0. A crash ensued. + class C(object): + def __init__(self, parent): + if not parent: + return + wself = weakref.ref(self) + def cb(wparent): + o = wself() + self.wparent = weakref.ref(parent, cb) + + d = weakref.WeakKeyDictionary() + root = c = C(None) + for n in range(100): + d[c] = c = C(c) + del root + gc.collect() + class SubclassableWeakrefTestCase(TestBase): @@ -809,17 +899,6 @@ self.assertEqual(self.cbcalled, 0) -class Object: - def __init__(self, arg): - self.arg = arg - def __repr__(self): - return "" % self.arg - -class RefCycle: - def __init__(self): - self.cycle = self - - class MappingTestCase(TestBase): COUNT = 10 diff --git a/lib-python/2.7/test/test_winreg.py b/lib-python/2.7/test/test_winreg.py --- a/lib-python/2.7/test/test_winreg.py +++ b/lib-python/2.7/test/test_winreg.py @@ -1,7 +1,7 @@ # Test the windows specific win32reg module. # Only win32reg functions not hit here: FlushKey, LoadKey and SaveKey -import os, sys +import os, sys, errno import unittest from test import test_support threading = test_support.import_module("threading") @@ -234,7 +234,7 @@ def test_changing_value(self): # Issue2810: A race condition in 2.6 and 3.1 may cause - # EnumValue or QueryValue to throw "WindowsError: More data is + # EnumValue or QueryValue to raise "WindowsError: More data is # available" done = False @@ -267,7 +267,7 @@ def test_long_key(self): # Issue2810, in 2.6 and 3.1 when the key name was exactly 256 - # characters, EnumKey threw "WindowsError: More data is + # characters, EnumKey raised "WindowsError: More data is # available" name = 'x'*256 try: @@ -282,8 +282,14 @@ def test_dynamic_key(self): # Issue2810, when the value is dynamically generated, these - # throw "WindowsError: More data is available" in 2.6 and 3.1 - EnumValue(HKEY_PERFORMANCE_DATA, 0) + # raise "WindowsError: More data is available" in 2.6 and 3.1 + try: + EnumValue(HKEY_PERFORMANCE_DATA, 0) + except OSError as e: + if e.errno in (errno.EPERM, errno.EACCES): + self.skipTest("access denied to registry key " + "(are you running in a non-interactive session?)") + raise QueryValueEx(HKEY_PERFORMANCE_DATA, None) # Reflection requires XP x64/Vista at a minimum. XP doesn't have this stuff @@ -308,6 +314,35 @@ finally: DeleteKey(HKEY_CURRENT_USER, test_key_name) + def test_setvalueex_value_range(self): + # Test for Issue #14420, accept proper ranges for SetValueEx. + # Py2Reg, which gets called by SetValueEx, was using PyLong_AsLong, + # thus raising OverflowError. The implementation now uses + # PyLong_AsUnsignedLong to match DWORD's size. + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + SetValueEx(ck, "test_name", None, REG_DWORD, 0x80000000) + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + def test_queryvalueex_return_value(self): + # Test for Issue #16759, return unsigned int from QueryValueEx. + # Reg2Py, which gets called by QueryValueEx, was returning a value + # generated by PyLong_FromLong. The implmentation now uses + # PyLong_FromUnsignedLong to match DWORD's size. + try: + with CreateKey(HKEY_CURRENT_USER, test_key_name) as ck: + self.assertNotEqual(ck.handle, 0) + test_val = 0x80000000 + SetValueEx(ck, "test_name", None, REG_DWORD, test_val) + ret_val, ret_type = QueryValueEx(ck, "test_name") + self.assertEqual(ret_type, REG_DWORD) + self.assertEqual(ret_val, test_val) + finally: + DeleteKey(HKEY_CURRENT_USER, test_key_name) + + @unittest.skipUnless(REMOTE_NAME, "Skipping remote registry tests") class RemoteWinregTests(BaseWinregTests): diff --git a/lib-python/2.7/test/test_winsound.py b/lib-python/2.7/test/test_winsound.py --- a/lib-python/2.7/test/test_winsound.py +++ b/lib-python/2.7/test/test_winsound.py @@ -2,6 +2,7 @@ import unittest from test import test_support +test_support.requires('audio') import time import os import subprocess diff --git a/lib-python/2.7/test/test_wsgiref.py b/lib-python/2.7/test/test_wsgiref.py --- a/lib-python/2.7/test/test_wsgiref.py +++ b/lib-python/2.7/test/test_wsgiref.py @@ -39,9 +39,6 @@ pass - - - def hello_app(environ,start_response): start_response("200 OK", [ ('Content-Type','text/plain'), @@ -62,27 +59,6 @@ return out.getvalue(), err.getvalue() - - - - - - - - - - - - - - - - - - - - - def compare_generic_iter(make_it,match): """Utility to compare a generic 2.1/2.2+ iterator with an iterable @@ -120,10 +96,6 @@ raise AssertionError("Too many items from .next()",it) - - - - class IntegrationTests(TestCase): def check_hello(self, out, has_length=True): @@ -161,10 +133,6 @@ ) - - - - class UtilityTests(TestCase): def checkShift(self,sn_in,pi_in,part,sn_out,pi_out): @@ -201,11 +169,6 @@ util.setup_testing_defaults(kw) self.assertEqual(util.request_uri(kw,query),uri) - - - - - def checkFW(self,text,size,match): def make_it(text=text,size=size): @@ -224,7 +187,6 @@ it.close() self.assertTrue(it.filelike.closed) - def testSimpleShifts(self): self.checkShift('','/', '', '/', '') self.checkShift('','/x', 'x', '/x', '') @@ -232,7 +194,6 @@ self.checkShift('/a','/x/y', 'x', '/a/x', '/y') self.checkShift('/a','/x/', 'x', '/a/x', '/') - def testNormalizedShifts(self): self.checkShift('/a/b', '/../y', '..', '/a', '/y') self.checkShift('', '/../y', '..', '', '/y') @@ -246,7 +207,6 @@ self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/') self.checkShift('/a/b', '/.', None, '/a/b', '') - def testDefaults(self): for key, value in [ ('SERVER_NAME','127.0.0.1'), @@ -266,7 +226,6 @@ ]: self.checkDefault(key,value) - def testCrossDefaults(self): self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar") self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on") @@ -276,7 +235,6 @@ self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo") self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on") - def testGuessScheme(self): self.assertEqual(util.guess_scheme({}), "http") self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http") @@ -284,10 +242,6 @@ self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https") self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https") - - - - def testAppURIs(self): self.checkAppURI("http://127.0.0.1/") self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam") @@ -411,15 +365,6 @@ raise # for testing, we want to see what's happening - - - - - - - - - class HandlerTests(TestCase): def checkEnvironAttrs(self, handler): @@ -460,7 +405,6 @@ h=TestHandler(); h.setup_environ() self.assertEqual(h.environ['wsgi.url_scheme'],'http') - def testAbstractMethods(self): h = BaseHandler() for name in [ @@ -469,7 +413,6 @@ self.assertRaises(NotImplementedError, getattr(h,name)) self.assertRaises(NotImplementedError, h._write, "test") - def testContentLength(self): # Demo one reason iteration is better than write()... ;) @@ -549,7 +492,6 @@ "\r\n"+MSG) self.assertNotEqual(h.stderr.getvalue().find("AssertionError"), -1) - def testHeaderFormats(self): def non_error_app(e,s): @@ -591,40 +533,28 @@ (stdpat%(version,sw), h.stdout.getvalue()) ) -# This epilogue is needed for compatibility with the Python 2.5 regrtest module + def testCloseOnError(self): + side_effects = {'close_called': False} + MSG = b"Some output has been sent" + def error_app(e,s): + s("200 OK",[])(MSG) + class CrashyIterable(object): + def __iter__(self): + while True: + yield b'blah' + raise AssertionError("This should be caught by handler") + + def close(self): + side_effects['close_called'] = True + return CrashyIterable() + + h = ErrorHandler() + h.run(error_app) + self.assertEqual(side_effects['close_called'], True) + def test_main(): test_support.run_unittest(__name__) if __name__ == "__main__": test_main() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# the above lines intentionally left blank diff --git a/lib-python/2.7/test/test_xml_etree.py b/lib-python/2.7/test/test_xml_etree.py --- a/lib-python/2.7/test/test_xml_etree.py +++ b/lib-python/2.7/test/test_xml_etree.py @@ -1822,6 +1822,26 @@ """ +def check_html_empty_elems_serialization(self): + # issue 15970 + # from http://www.w3.org/TR/html401/index/elements.html + """ + + >>> empty_elems = ['AREA', 'BASE', 'BASEFONT', 'BR', 'COL', 'FRAME', 'HR', + ... 'IMG', 'INPUT', 'ISINDEX', 'LINK', 'META', 'PARAM'] + >>> elems = ''.join('<%s />' % elem for elem in empty_elems) + >>> serialize(ET.XML('%s' % elems), method='html') + '

' + >>> serialize(ET.XML('%s' % elems.lower()), method='html') + '

' + >>> elems = ''.join('<%s>' % (elem, elem) for elem in empty_elems) + >>> serialize(ET.XML('%s' % elems), method='html') + '

' + >>> serialize(ET.XML('%s' % elems.lower()), method='html') + '

' + + """ + # -------------------------------------------------------------------- diff --git a/lib-python/2.7/test/test_xrange.py b/lib-python/2.7/test/test_xrange.py --- a/lib-python/2.7/test/test_xrange.py +++ b/lib-python/2.7/test/test_xrange.py @@ -46,6 +46,28 @@ self.fail('{}: wrong element at position {};' 'expected {}, got {}'.format(test_id, i, y, x)) + def assert_xranges_equivalent(self, x, y): + # Check that two xrange objects are equivalent, in the sense of the + # associated sequences being the same. We want to use this for large + # xrange objects, so instead of converting to lists and comparing + # directly we do a number of indirect checks. + if len(x) != len(y): + self.fail('{} and {} have different ' + 'lengths: {} and {} '.format(x, y, len(x), len(y))) + if len(x) >= 1: + if x[0] != y[0]: + self.fail('{} and {} have different initial ' + 'elements: {} and {} '.format(x, y, x[0], y[0])) + if x[-1] != y[-1]: + self.fail('{} and {} have different final ' + 'elements: {} and {} '.format(x, y, x[-1], y[-1])) + if len(x) >= 2: + x_step = x[1] - x[0] + y_step = y[1] - y[0] + if x_step != y_step: + self.fail('{} and {} have different step: ' + '{} and {} '.format(x, y, x_step, y_step)) + def test_xrange(self): self.assertEqual(list(xrange(3)), [0, 1, 2]) self.assertEqual(list(xrange(1, 5)), [1, 2, 3, 4]) @@ -104,6 +126,59 @@ self.assertEqual(list(pickle.loads(pickle.dumps(r, proto))), list(r)) + M = min(sys.maxint, sys.maxsize) + large_testcases = testcases + [ + (0, M, 1), + (M, 0, -1), + (0, M, M - 1), + (M // 2, M, 1), + (0, -M, -1), + (0, -M, 1 - M), + (-M, M, 2), + (-M, M, 1024), + (-M, M, 10585), + (M, -M, -2), + (M, -M, -1024), + (M, -M, -10585), + ] + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + for t in large_testcases: + r = xrange(*t) + r_out = pickle.loads(pickle.dumps(r, proto)) + self.assert_xranges_equivalent(r_out, r) + + def test_repr(self): + # Check that repr of an xrange is a valid representation + # of that xrange. + + # Valid xranges have at most min(sys.maxint, sys.maxsize) elements. + M = min(sys.maxint, sys.maxsize) + + testcases = [ + (13,), + (0, 11), + (-22, 10), + (20, 3, -1), + (13, 21, 3), + (-2, 2, 2), + (0, M, 1), + (M, 0, -1), + (0, M, M - 1), + (M // 2, M, 1), + (0, -M, -1), + (0, -M, 1 - M), + (-M, M, 2), + (-M, M, 1024), + (-M, M, 10585), + (M, -M, -2), + (M, -M, -1024), + (M, -M, -10585), + ] + for t in testcases: + r = xrange(*t) + r_out = eval(repr(r)) + self.assert_xranges_equivalent(r, r_out) + def test_range_iterators(self): # see issue 7298 limits = [base + jiggle diff --git a/lib-python/2.7/test/test_zipfile.py b/lib-python/2.7/test/test_zipfile.py --- a/lib-python/2.7/test/test_zipfile.py +++ b/lib-python/2.7/test/test_zipfile.py @@ -26,7 +26,7 @@ SMALL_TEST_DATA = [('_ziptest1', '1q2w3e4r5t'), ('ziptest2dir/_ziptest2', 'qawsedrftg'), - ('/ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'), + ('ziptest2dir/ziptest3dir/_ziptest3', 'azsxdcfvgb'), ('ziptest2dir/ziptest3dir/ziptest4dir/_ziptest3', '6y7u8i9o0p')] @@ -391,10 +391,7 @@ writtenfile = zipfp.extract(fpath) # make sure it was written to the right place - if os.path.isabs(fpath): - correctfile = os.path.join(os.getcwd(), fpath[1:]) - else: - correctfile = os.path.join(os.getcwd(), fpath) + correctfile = os.path.join(os.getcwd(), fpath) correctfile = os.path.normpath(correctfile) self.assertEqual(writtenfile, correctfile) @@ -414,10 +411,7 @@ with zipfile.ZipFile(TESTFN2, "r") as zipfp: zipfp.extractall() for fpath, fdata in SMALL_TEST_DATA: - if os.path.isabs(fpath): - outfile = os.path.join(os.getcwd(), fpath[1:]) - else: - outfile = os.path.join(os.getcwd(), fpath) + outfile = os.path.join(os.getcwd(), fpath) self.assertEqual(fdata, open(outfile, "rb").read()) os.remove(outfile) @@ -425,6 +419,92 @@ # remove the test file subdirectories shutil.rmtree(os.path.join(os.getcwd(), 'ziptest2dir')) + def check_file(self, filename, content): + self.assertTrue(os.path.isfile(filename)) + with open(filename, 'rb') as f: + self.assertEqual(f.read(), content) + + def test_extract_hackers_arcnames(self): + hacknames = [ + ('../foo/bar', 'foo/bar'), + ('foo/../bar', 'foo/bar'), + ('foo/../../bar', 'foo/bar'), + ('foo/bar/..', 'foo/bar'), + ('./../foo/bar', 'foo/bar'), + ('/foo/bar', 'foo/bar'), + ('/foo/../bar', 'foo/bar'), + ('/foo/../../bar', 'foo/bar'), + ] + if os.path.sep == '\\': + hacknames.extend([ + (r'..\foo\bar', 'foo/bar'), + (r'..\/foo\/bar', 'foo/bar'), + (r'foo/\..\/bar', 'foo/bar'), + (r'foo\/../\bar', 'foo/bar'), + (r'C:foo/bar', 'foo/bar'), + (r'C:/foo/bar', 'foo/bar'), + (r'C://foo/bar', 'foo/bar'), + (r'C:\foo\bar', 'foo/bar'), + (r'//conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'), + (r'\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'), + (r'///conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'), + (r'\\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'), + (r'//conky//mountpoint/foo/bar', 'conky/mountpoint/foo/bar'), + (r'\\conky\\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'), + (r'//?/C:/foo/bar', '_/C_/foo/bar'), + (r'\\?\C:\foo\bar', '_/C_/foo/bar'), + (r'C:/../C:/foo/bar', 'C_/foo/bar'), + (r'a:b\ce|f"g?h*i', 'b/c_d_e_f_g_h_i'), + ('../../foo../../ba..r', 'foo/ba..r'), + ]) + else: # Unix + hacknames.extend([ + ('//foo/bar', 'foo/bar'), + ('../../foo../../ba..r', 'foo../ba..r'), + (r'foo/..\bar', r'foo/..\bar'), + ]) + + for arcname, fixedname in hacknames: + content = b'foobar' + arcname.encode() + with zipfile.ZipFile(TESTFN2, 'w', zipfile.ZIP_STORED) as zipfp: + zinfo = zipfile.ZipInfo() + # preserve backslashes + zinfo.filename = arcname + zinfo.external_attr = 0o600 << 16 + zipfp.writestr(zinfo, content) + + arcname = arcname.replace(os.sep, "/") + targetpath = os.path.join('target', 'subdir', 'subsub') + correctfile = os.path.join(targetpath, *fixedname.split('/')) + + with zipfile.ZipFile(TESTFN2, 'r') as zipfp: + writtenfile = zipfp.extract(arcname, targetpath) + self.assertEqual(writtenfile, correctfile, + msg="extract %r" % arcname) + self.check_file(correctfile, content) + shutil.rmtree('target') + + with zipfile.ZipFile(TESTFN2, 'r') as zipfp: + zipfp.extractall(targetpath) + self.check_file(correctfile, content) + shutil.rmtree('target') + + correctfile = os.path.join(os.getcwd(), *fixedname.split('/')) + + with zipfile.ZipFile(TESTFN2, 'r') as zipfp: + writtenfile = zipfp.extract(arcname) + self.assertEqual(writtenfile, correctfile, + msg="extract %r" % arcname) + self.check_file(correctfile, content) + shutil.rmtree(fixedname.split('/')[0]) + + with zipfile.ZipFile(TESTFN2, 'r') as zipfp: + zipfp.extractall() + self.check_file(correctfile, content) + shutil.rmtree(fixedname.split('/')[0]) + + os.remove(TESTFN2) + def test_writestr_compression(self): zipfp = zipfile.ZipFile(TESTFN2, "w") zipfp.writestr("a.txt", "hello world", compress_type=zipfile.ZIP_STORED) @@ -760,6 +840,20 @@ chk = zipfile.is_zipfile(fp) self.assertTrue(not chk) + def test_damaged_zipfile(self): + """Check that zipfiles with missing bytes at the end raise BadZipFile.""" + # - Create a valid zip file + fp = io.BytesIO() + with zipfile.ZipFile(fp, mode="w") as zipf: + zipf.writestr("foo.txt", b"O, for a Muse of Fire!") + zipfiledata = fp.getvalue() + + # - Now create copies of it missing the last N bytes and make sure + # a BadZipFile exception is raised when we try to open it + for N in range(len(zipfiledata)): + fp = io.BytesIO(zipfiledata[:N]) + self.assertRaises(zipfile.BadZipfile, zipfile.ZipFile, fp) + def test_is_zip_valid_file(self): """Check that is_zipfile() correctly identifies zip files.""" # - passing a filename @@ -811,7 +905,7 @@ with zipfile.ZipFile(data, mode="w") as zipf: zipf.writestr("foo.txt", "O, for a Muse of Fire!") - # This is correct; calling .read on a closed ZipFile should throw + # This is correct; calling .read on a closed ZipFile should raise # a RuntimeError, and so should calling .testzip. An earlier # version of .testzip would swallow this exception (and any other) # and report that the first file in the archive was corrupt. @@ -859,6 +953,17 @@ caught.""" self.assertRaises(RuntimeError, zipfile.ZipFile, TESTFN, "w", -1) + def test_unsupported_compression(self): + # data is declared as shrunk, but actually deflated + data = (b'PK\x03\x04.\x00\x00\x00\x01\x00\xe4C\xa1@\x00\x00\x00' + b'\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00x\x03\x00PK\x01' + b'\x02.\x03.\x00\x00\x00\x01\x00\xe4C\xa1@\x00\x00\x00\x00\x02\x00\x00' + b'\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x80\x01\x00\x00\x00\x00xPK\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00' + b'/\x00\x00\x00!\x00\x00\x00\x00\x00') + with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf: + self.assertRaises(NotImplementedError, zipf.open, 'x') + def test_null_byte_in_filename(self): """Check that a filename containing a null byte is properly terminated.""" @@ -908,6 +1013,22 @@ with zipfile.ZipFile(TESTFN, mode="r") as zipf: self.assertEqual(zipf.comment, comment2) + def test_change_comment_in_empty_archive(self): + with zipfile.ZipFile(TESTFN, "a", zipfile.ZIP_STORED) as zipf: + self.assertFalse(zipf.filelist) + zipf.comment = b"this is a comment" + with zipfile.ZipFile(TESTFN, "r") as zipf: + self.assertEqual(zipf.comment, b"this is a comment") + + def test_change_comment_in_nonempty_archive(self): + with zipfile.ZipFile(TESTFN, "w", zipfile.ZIP_STORED) as zipf: + zipf.writestr("foo.txt", "O, for a Muse of Fire!") + with zipfile.ZipFile(TESTFN, "a", zipfile.ZIP_STORED) as zipf: + self.assertTrue(zipf.filelist) + zipf.comment = b"this is a comment" + with zipfile.ZipFile(TESTFN, "r") as zipf: + self.assertEqual(zipf.comment, b"this is a comment") + def check_testzip_with_bad_crc(self, compression): """Tests that files with bad CRCs return their name from testzip.""" zipdata = self.zips_with_bad_crc[compression] diff --git a/lib-python/2.7/test/test_zipimport_support.py b/lib-python/2.7/test/test_zipimport_support.py --- a/lib-python/2.7/test/test_zipimport_support.py +++ b/lib-python/2.7/test/test_zipimport_support.py @@ -29,7 +29,8 @@ # test_cmd_line_script (covers the zipimport support in runpy) # Retrieve some helpers from other test cases -from test import test_doctest, sample_doctest +from test import (test_doctest, sample_doctest, sample_doctest_no_doctests, + sample_doctest_no_docstrings) from test.test_importhooks import ImportHooksBaseTestCase @@ -99,16 +100,26 @@ "test_zipped_doctest") test_src = test_src.replace("test.sample_doctest", "sample_zipped_doctest") - sample_src = inspect.getsource(sample_doctest) - sample_src = sample_src.replace("test.test_doctest", - "test_zipped_doctest") + # The sample doctest files rewritten to include in the zipped version. + sample_sources = {} + for mod in [sample_doctest, sample_doctest_no_doctests, + sample_doctest_no_docstrings]: + src = inspect.getsource(mod) + src = src.replace("test.test_doctest", "test_zipped_doctest") + # Rewrite the module name so that, for example, + # "test.sample_doctest" becomes "sample_zipped_doctest". + mod_name = mod.__name__.split(".")[-1] + mod_name = mod_name.replace("sample_", "sample_zipped_") + sample_sources[mod_name] = src + with temp_dir() as d: script_name = make_script(d, 'test_zipped_doctest', test_src) zip_name, run_name = make_zip_script(d, 'test_zip', script_name) z = zipfile.ZipFile(zip_name, 'a') - z.writestr("sample_zipped_doctest.py", sample_src) + for mod_name, src in sample_sources.items(): + z.writestr(mod_name + ".py", src) z.close() if verbose: zip_file = zipfile.ZipFile(zip_name, 'r') @@ -168,9 +179,10 @@ test_zipped_doctest.test_unittest_reportflags, ] # Needed for test_DocTestParser and test_debug - deprecations = [ + deprecations = [] + if __debug__: # Ignore all warnings about the use of class Tester in this module. - ("class Tester is deprecated", DeprecationWarning)] + deprecations.append(("class Tester is deprecated", DeprecationWarning)) if sys.py3kwarning: deprecations += [ ("backquote not supported", SyntaxWarning), diff --git a/lib-python/2.7/test/test_zlib.py b/lib-python/2.7/test/test_zlib.py --- a/lib-python/2.7/test/test_zlib.py +++ b/lib-python/2.7/test/test_zlib.py @@ -396,6 +396,18 @@ y += dco.flush() self.assertEqual(y, 'foo') + def test_flush_with_freed_input(self): + # Issue #16411: decompressor accesses input to last decompress() call + # in flush(), even if this object has been freed in the meanwhile. + input1 = 'abcdefghijklmnopqrstuvwxyz' + input2 = 'QWERTYUIOPASDFGHJKLZXCVBNM' + data = zlib.compress(input1) + dco = zlib.decompressobj() + dco.decompress(data, 1) + del data + data = zlib.compress(input2) + self.assertEqual(dco.flush(), input1[1:]) + if hasattr(zlib.compressobj(), "copy"): def test_compresscopy(self): # Test copying a compression object @@ -426,6 +438,31 @@ c.flush() self.assertRaises(ValueError, c.copy) + def test_decompress_unused_data(self): + # Repeated calls to decompress() after EOF should accumulate data in + # dco.unused_data, instead of just storing the arg to the last call. + source = b'abcdefghijklmnopqrstuvwxyz' + remainder = b'0123456789' + y = zlib.compress(source) + x = y + remainder + for maxlen in 0, 1000: + for step in 1, 2, len(y), len(x): + dco = zlib.decompressobj() + data = b'' + for i in range(0, len(x), step): + if i < len(y): + self.assertEqual(dco.unused_data, b'') + if maxlen == 0: + data += dco.decompress(x[i : i + step]) + self.assertEqual(dco.unconsumed_tail, b'') + else: + data += dco.decompress( + dco.unconsumed_tail + x[i : i + step], maxlen) + data += dco.flush() + self.assertEqual(data, source) + self.assertEqual(dco.unconsumed_tail, b'') + self.assertEqual(dco.unused_data, remainder) + if hasattr(zlib.decompressobj(), "copy"): def test_decompresscopy(self): # Test copying a decompression object diff --git a/lib-python/2.7/test/testbz2_bigmem.bz2 b/lib-python/2.7/test/testbz2_bigmem.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..c9a4616c8053b1c92a45023635c8610c26299fc2 GIT binary patch [stripped] diff --git a/lib-python/2.7/test/testtar.tar b/lib-python/2.7/test/testtar.tar index bac0e2628f35243f236db2fac82737882699b2f0..440182a437da84ef3d61d8cbc0f4209254615b37 GIT binary patch [stripped] diff --git a/lib-python/2.7/textwrap.py b/lib-python/2.7/textwrap.py --- a/lib-python/2.7/textwrap.py +++ b/lib-python/2.7/textwrap.py @@ -9,6 +9,14 @@ import string, re +try: + _unicode = unicode +except NameError: + # If Python is built without Unicode support, the unicode type + # will not exist. Fake one. + class _unicode(object): + pass + # Do the right thing with boolean values for all known Python versions # (so this module can be copied to projects that don't depend on Python # 2.3, e.g. Optik and Docutils) by uncommenting the block of code below. @@ -147,7 +155,7 @@ if self.replace_whitespace: if isinstance(text, str): text = text.translate(self.whitespace_trans) - elif isinstance(text, unicode): + elif isinstance(text, _unicode): text = text.translate(self.unicode_whitespace_trans) return text @@ -167,7 +175,7 @@ 'use', ' ', 'the', ' ', '-b', ' ', option!' otherwise. """ - if isinstance(text, unicode): + if isinstance(text, _unicode): if self.break_on_hyphens: pat = self.wordsep_re_uni else: diff --git a/lib-python/2.7/threading.py b/lib-python/2.7/threading.py --- a/lib-python/2.7/threading.py +++ b/lib-python/2.7/threading.py @@ -10,6 +10,7 @@ import warnings +from collections import deque as _deque from time import time as _time, sleep as _sleep from traceback import format_exc as _format_exc @@ -605,6 +606,10 @@ pass def __stop(self): + # DummyThreads delete self.__block, but they have no waiters to + # notify anyway (join() is forbidden on them). + if not hasattr(self, '_Thread__block'): + return self.__block.acquire() self.__stopped = True self.__block.notify_all() @@ -909,7 +914,7 @@ self.rc = Condition(self.mon) self.wc = Condition(self.mon) self.limit = limit - self.queue = deque() + self.queue = _deque() def put(self, item): self.mon.acquire() diff --git a/lib-python/2.7/tokenize.py b/lib-python/2.7/tokenize.py --- a/lib-python/2.7/tokenize.py +++ b/lib-python/2.7/tokenize.py @@ -70,10 +70,10 @@ Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''" # Tail end of """ string. Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""' -Triple = group("[uU]?[rR]?'''", '[uU]?[rR]?"""') +Triple = group("[uUbB]?[rR]?'''", '[uUbB]?[rR]?"""') # Single-line ' or " string. -String = group(r"[uU]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*'", - r'[uU]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*"') +String = group(r"[uUbB]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*'", + r'[uUbB]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*"') # Because of leftmost-then-longest match semantics, be sure to put the # longest operators first (e.g., if = came before ==, == would get @@ -91,11 +91,11 @@ Token = Ignore + PlainToken # First (or only) line of ' or " string. -ContStr = group(r"[uU]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*" + +ContStr = group(r"[uUbB]?[rR]?'[^\n'\\]*(?:\\.[^\n'\\]*)*" + group("'", r'\\\r?\n'), - r'[uU]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*' + + r'[uUbB]?[rR]?"[^\n"\\]*(?:\\.[^\n"\\]*)*' + group('"', r'\\\r?\n')) -PseudoExtras = group(r'\\\r?\n', Comment, Triple) +PseudoExtras = group(r'\\\r?\n|\Z', Comment, Triple) PseudoToken = Whitespace + group(PseudoExtras, Number, Funny, ContStr, Name) tokenprog, pseudoprog, single3prog, double3prog = map( @@ -362,6 +362,8 @@ if pseudomatch: # scan for tokens start, end = pseudomatch.span(1) spos, epos, pos = (lnum, start), (lnum, end), end + if start == end: + continue token, initial = line[start:end], line[start] if initial in numchars or \ diff --git a/lib-python/2.7/traceback.py b/lib-python/2.7/traceback.py --- a/lib-python/2.7/traceback.py +++ b/lib-python/2.7/traceback.py @@ -166,7 +166,7 @@ # >>> raise string1, string2 # deprecated # # Clear these out first because issubtype(string1, SyntaxError) - # would throw another exception and mask the original problem. + # would raise another exception and mask the original problem. if (isinstance(etype, BaseException) or isinstance(etype, types.InstanceType) or etype is None or type(etype) is str): diff --git a/lib-python/2.7/unittest/case.py b/lib-python/2.7/unittest/case.py --- a/lib-python/2.7/unittest/case.py +++ b/lib-python/2.7/unittest/case.py @@ -6,6 +6,7 @@ import difflib import pprint import re +import types import warnings from . import result @@ -55,7 +56,7 @@ Unconditionally skip a test. """ def decorator(test_item): - if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): + if not isinstance(test_item, (type, types.ClassType)): @functools.wraps(test_item) def skip_wrapper(*args, **kwargs): raise SkipTest(reason) @@ -201,7 +202,11 @@ self.addTypeEqualityFunc(tuple, 'assertTupleEqual') self.addTypeEqualityFunc(set, 'assertSetEqual') self.addTypeEqualityFunc(frozenset, 'assertSetEqual') - self.addTypeEqualityFunc(unicode, 'assertMultiLineEqual') + try: + self.addTypeEqualityFunc(unicode, 'assertMultiLineEqual') + except NameError: + # No unicode support in this build + pass def addTypeEqualityFunc(self, typeobj, function): """Add a type specific assertEqual style function to compare a type. @@ -442,10 +447,10 @@ def assertRaises(self, excClass, callableObj=None, *args, **kwargs): - """Fail unless an exception of class excClass is thrown + """Fail unless an exception of class excClass is raised by callableObj when invoked with arguments args and keyword arguments kwargs. If a different type of exception is - thrown, it will not be caught, and the test case will be + raised, it will not be caught, and the test case will be deemed to have suffered an error, exactly as for an unexpected exception. @@ -511,7 +516,7 @@ assertion_func(first, second, msg=msg) def assertNotEqual(self, first, second, msg=None): - """Fail if the two objects are equal as determined by the '==' + """Fail if the two objects are equal as determined by the '!=' operator. """ if not first != second: @@ -871,7 +876,7 @@ - [0, 1, 1] and [1, 0, 1] compare equal. - [0, 0, 1] and [0, 1] compare unequal. """ - first_seq, second_seq = list(actual_seq), list(expected_seq) + first_seq, second_seq = list(expected_seq), list(actual_seq) with warnings.catch_warnings(): if sys.py3kwarning: # Silence Py3k warning raised during the sorting diff --git a/lib-python/2.7/unittest/main.py b/lib-python/2.7/unittest/main.py --- a/lib-python/2.7/unittest/main.py +++ b/lib-python/2.7/unittest/main.py @@ -157,7 +157,10 @@ self.test = self.testLoader.loadTestsFromNames(self.testNames, self.module) - def _do_discovery(self, argv, Loader=loader.TestLoader): + def _do_discovery(self, argv, Loader=None): + if Loader is None: + Loader = lambda: self.testLoader + # handle command line args for test discovery self.progName = '%s discover' % self.progName import optparse diff --git a/lib-python/2.7/unittest/runner.py b/lib-python/2.7/unittest/runner.py --- a/lib-python/2.7/unittest/runner.py +++ b/lib-python/2.7/unittest/runner.py @@ -34,7 +34,7 @@ separator2 = '-' * 70 def __init__(self, stream, descriptions, verbosity): - super(TextTestResult, self).__init__() + super(TextTestResult, self).__init__(stream, descriptions, verbosity) self.stream = stream self.showAll = verbosity > 1 self.dots = verbosity == 1 diff --git a/lib-python/2.7/unittest/signals.py b/lib-python/2.7/unittest/signals.py --- a/lib-python/2.7/unittest/signals.py +++ b/lib-python/2.7/unittest/signals.py @@ -9,6 +9,20 @@ class _InterruptHandler(object): def __init__(self, default_handler): self.called = False + self.original_handler = default_handler + if isinstance(default_handler, int): + if default_handler == signal.SIG_DFL: + # Pretend it's signal.default_int_handler instead. + default_handler = signal.default_int_handler + elif default_handler == signal.SIG_IGN: + # Not quite the same thing as SIG_IGN, but the closest we + # can make it: do nothing. + def default_handler(unused_signum, unused_frame): + pass + else: + raise TypeError("expected SIGINT signal handler to be " + "signal.SIG_IGN, signal.SIG_DFL, or a " + "callable object") self.default_handler = default_handler def __call__(self, signum, frame): @@ -54,4 +68,4 @@ global _interrupt_handler if _interrupt_handler is not None: - signal.signal(signal.SIGINT, _interrupt_handler.default_handler) + signal.signal(signal.SIGINT, _interrupt_handler.original_handler) diff --git a/lib-python/2.7/unittest/test/test_break.py b/lib-python/2.7/unittest/test/test_break.py --- a/lib-python/2.7/unittest/test/test_break.py +++ b/lib-python/2.7/unittest/test/test_break.py @@ -15,9 +15,12 @@ @unittest.skipIf(sys.platform == 'freebsd6', "Test kills regrtest on freebsd6 " "if threads have been used") class TestBreak(unittest.TestCase): + int_handler = None def setUp(self): self._default_handler = signal.getsignal(signal.SIGINT) + if self.int_handler is not None: + signal.signal(signal.SIGINT, self.int_handler) def tearDown(self): signal.signal(signal.SIGINT, self._default_handler) @@ -74,6 +77,10 @@ def testSecondInterrupt(self): + # Can't use skipIf decorator because the signal handler may have + # been changed after defining this method. + if signal.getsignal(signal.SIGINT) == signal.SIG_IGN: + self.skipTest("test requires SIGINT to not be ignored") result = unittest.TestResult() unittest.installHandler() unittest.registerResult(result) @@ -123,6 +130,10 @@ def testHandlerReplacedButCalled(self): + # Can't use skipIf decorator because the signal handler may have + # been changed after defining this method. + if signal.getsignal(signal.SIGINT) == signal.SIG_IGN: + self.skipTest("test requires SIGINT to not be ignored") # If our handler has been replaced (is no longer installed) but is # called by the *new* handler, then it isn't safe to delay the # SIGINT and we should immediately delegate to the default handler @@ -250,3 +261,24 @@ test() self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + at unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") + at unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") + at unittest.skipIf(sys.platform == 'freebsd6', "Test kills regrtest on freebsd6 " + "if threads have been used") +class TestBreakDefaultIntHandler(TestBreak): + int_handler = signal.default_int_handler + + at unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") + at unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") + at unittest.skipIf(sys.platform == 'freebsd6', "Test kills regrtest on freebsd6 " + "if threads have been used") +class TestBreakSignalIgnored(TestBreak): + int_handler = signal.SIG_IGN + + at unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill") + at unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows") + at unittest.skipIf(sys.platform == 'freebsd6', "Test kills regrtest on freebsd6 " + "if threads have been used") +class TestBreakSignalDefault(TestBreak): + int_handler = signal.SIG_DFL diff --git a/lib-python/2.7/unittest/test/test_discovery.py b/lib-python/2.7/unittest/test/test_discovery.py --- a/lib-python/2.7/unittest/test/test_discovery.py +++ b/lib-python/2.7/unittest/test/test_discovery.py @@ -220,12 +220,26 @@ program = object.__new__(unittest.TestProgram) program.usageExit = usageExit + program.testLoader = None with self.assertRaises(Stop): # too many args program._do_discovery(['one', 'two', 'three', 'four']) + def test_command_line_handling_do_discovery_uses_default_loader(self): + program = object.__new__(unittest.TestProgram) + + class Loader(object): + args = [] + def discover(self, start_dir, pattern, top_level_dir): + self.args.append((start_dir, pattern, top_level_dir)) + return 'tests' + + program.testLoader = Loader() + program._do_discovery(['-v']) + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + def test_command_line_handling_do_discovery_calls_loader(self): program = object.__new__(unittest.TestProgram) diff --git a/lib-python/2.7/unittest/test/test_runner.py b/lib-python/2.7/unittest/test/test_runner.py --- a/lib-python/2.7/unittest/test/test_runner.py +++ b/lib-python/2.7/unittest/test/test_runner.py @@ -149,6 +149,19 @@ self.assertEqual(runner.resultclass, unittest.TextTestResult) + def test_multiple_inheritance(self): + class AResult(unittest.TestResult): + def __init__(self, stream, descriptions, verbosity): + super(AResult, self).__init__(stream, descriptions, verbosity) + + class ATextResult(unittest.TextTestResult, AResult): + pass + + # This used to raise an exception due to TextTestResult not passing + # on arguments in its __init__ super call + ATextResult(None, None, None) + + def testBufferAndFailfast(self): class Test(unittest.TestCase): def testFoo(self): diff --git a/lib-python/2.7/unittest/test/test_skipping.py b/lib-python/2.7/unittest/test/test_skipping.py --- a/lib-python/2.7/unittest/test/test_skipping.py +++ b/lib-python/2.7/unittest/test/test_skipping.py @@ -66,6 +66,36 @@ self.assertEqual(result.skipped, [(test, "testing")]) self.assertEqual(record, []) + def test_skip_non_unittest_class_old_style(self): + @unittest.skip("testing") + class Mixin: + def test_1(self): + record.append(1) + class Foo(Mixin, unittest.TestCase): + pass + record = [] + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(record, []) + + def test_skip_non_unittest_class_new_style(self): + @unittest.skip("testing") + class Mixin(object): + def test_1(self): + record.append(1) + class Foo(Mixin, unittest.TestCase): + pass + record = [] + result = unittest.TestResult() + test = Foo("test_1") + suite = unittest.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(record, []) + def test_expected_failure(self): class Foo(unittest.TestCase): @unittest.expectedFailure diff --git a/lib-python/2.7/urllib2.py b/lib-python/2.7/urllib2.py --- a/lib-python/2.7/urllib2.py +++ b/lib-python/2.7/urllib2.py @@ -102,6 +102,7 @@ import time import urlparse import bisect +import warnings try: from cStringIO import StringIO @@ -109,7 +110,7 @@ from StringIO import StringIO from urllib import (unwrap, unquote, splittype, splithost, quote, - addinfourl, splitport, splittag, + addinfourl, splitport, splittag, toBytes, splitattr, ftpwrapper, splituser, splitpasswd, splitvalue) # support for FileHandler, proxies via environment variables @@ -172,6 +173,9 @@ def reason(self): return self.msg + def info(self): + return self.hdrs + # copied from cookielib.py _cut_port_re = re.compile(r":\d+$") def request_host(request): @@ -828,7 +832,7 @@ # allow for double- and single-quoted realm values # (single quotes are a violation of the RFC, but appear in the wild) rx = re.compile('(?:.*,)*[ \t]*([^ \t]+)[ \t]+' - 'realm=(["\'])(.*?)\\2', re.I) + 'realm=(["\']?)([^"\']*)\\2', re.I) # XXX could pre-emptively send auth info already accepted (RFC 2617, # end of section 2, and section 1.2 immediately after "credentials" @@ -861,6 +865,9 @@ mo = AbstractBasicAuthHandler.rx.search(authreq) if mo: scheme, quote, realm = mo.groups() + if quote not in ['"', "'"]: + warnings.warn("Basic Auth Realm was unquoted", + UserWarning, 2) if scheme.lower() == 'basic': response = self.retry_http_basic_auth(host, req, realm) if response and response.code != 401: diff --git a/lib-python/2.7/urlparse.py b/lib-python/2.7/urlparse.py --- a/lib-python/2.7/urlparse.py +++ b/lib-python/2.7/urlparse.py @@ -40,11 +40,14 @@ 'imap', 'wais', 'file', 'mms', 'https', 'shttp', 'snews', 'prospero', 'rtsp', 'rtspu', 'rsync', '', 'svn', 'svn+ssh', 'sftp','nfs','git', 'git+ssh'] +uses_params = ['ftp', 'hdl', 'prospero', 'http', 'imap', + 'https', 'shttp', 'rtsp', 'rtspu', 'sip', 'sips', + 'mms', '', 'sftp', 'tel'] + +# These are not actually used anymore, but should stay for backwards +# compatibility. (They are undocumented, but have a public-looking name.) non_hierarchical = ['gopher', 'hdl', 'mailto', 'news', 'telnet', 'wais', 'imap', 'snews', 'sip', 'sips'] -uses_params = ['ftp', 'hdl', 'prospero', 'http', 'imap', - 'https', 'shttp', 'rtsp', 'rtspu', 'sip', 'sips', - 'mms', '', 'sftp'] uses_query = ['http', 'wais', 'imap', 'https', 'shttp', 'mms', 'gopher', 'rtsp', 'rtspu', 'sip', 'sips', ''] uses_fragment = ['ftp', 'hdl', 'http', 'gopher', 'news', @@ -104,9 +107,11 @@ netloc = self.netloc.split('@')[-1].split(']')[-1] if ':' in netloc: port = netloc.split(':')[1] - return int(port, 10) - else: - return None + port = int(port, 10) + # verify legal port + if (0 <= port <= 65535): + return port + return None from collections import namedtuple @@ -192,21 +197,21 @@ if c not in scheme_chars: break else: - try: - # make sure "url" is not actually a port number (in which case - # "scheme" is really part of the path - _testportnum = int(url[i+1:]) - except ValueError: - scheme, url = url[:i].lower(), url[i+1:] + # make sure "url" is not actually a port number (in which case + # "scheme" is really part of the path) + rest = url[i+1:] + if not rest or any(c not in '0123456789' for c in rest): + # not a port number + scheme, url = url[:i].lower(), rest if url[:2] == '//': netloc, url = _splitnetloc(url, 2) if (('[' in netloc and ']' not in netloc) or (']' in netloc and '[' not in netloc)): raise ValueError("Invalid IPv6 URL") - if allow_fragments and scheme in uses_fragment and '#' in url: + if allow_fragments and '#' in url: url, fragment = url.split('#', 1) - if scheme in uses_query and '?' in url: + if '?' in url: url, query = url.split('?', 1) v = SplitResult(scheme, netloc, url, query, fragment) _parse_cache[key] = v diff --git a/lib-python/2.7/wave.py b/lib-python/2.7/wave.py --- a/lib-python/2.7/wave.py +++ b/lib-python/2.7/wave.py @@ -261,9 +261,9 @@ # def _read_fmt_chunk(self, chunk): - wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack(' prefix dicts self._current_context = self._ns_contexts[-1] self._undeclared_ns_maps = [] self._encoding = encoding - def _write(self, text): - if isinstance(text, str): - self._out.write(text) - else: - self._out.write(text.encode(self._encoding, _error_handling)) - def _qname(self, name): """Builds a qualified name from a (ns_url, localname) pair""" if name[0]: @@ -120,9 +138,12 @@ # ContentHandler methods def startDocument(self): - self._write('\n' % + self._write(u'\n' % self._encoding) + def endDocument(self): + self._flush() + def startPrefixMapping(self, prefix, uri): self._ns_contexts.append(self._current_context.copy()) self._current_context[uri] = prefix @@ -133,39 +154,39 @@ del self._ns_contexts[-1] def startElement(self, name, attrs): - self._write('<' + name) + self._write(u'<' + name) for (name, value) in attrs.items(): - self._write(' %s=%s' % (name, quoteattr(value))) - self._write('>') + self._write(u' %s=%s' % (name, quoteattr(value))) + self._write(u'>') def endElement(self, name): - self._write('' % name) + self._write(u'' % name) def startElementNS(self, name, qname, attrs): - self._write('<' + self._qname(name)) + self._write(u'<' + self._qname(name)) for prefix, uri in self._undeclared_ns_maps: if prefix: - self._out.write(' xmlns:%s="%s"' % (prefix, uri)) + self._write(u' xmlns:%s="%s"' % (prefix, uri)) else: - self._out.write(' xmlns="%s"' % uri) + self._write(u' xmlns="%s"' % uri) self._undeclared_ns_maps = [] for (name, value) in attrs.items(): - self._write(' %s=%s' % (self._qname(name), quoteattr(value))) - self._write('>') + self._write(u' %s=%s' % (self._qname(name), quoteattr(value))) + self._write(u'>') def endElementNS(self, name, qname): - self._write('' % self._qname(name)) + self._write(u'' % self._qname(name)) def characters(self, content): - self._write(escape(content)) + self._write(escape(unicode(content))) def ignorableWhitespace(self, content): - self._write(content) + self._write(unicode(content)) def processingInstruction(self, target, data): - self._write('' % (target, data)) + self._write(u'' % (target, data)) class XMLFilterBase(xmlreader.XMLReader): @@ -293,14 +314,31 @@ source.setSystemId(f.name) if source.getByteStream() is None: - sysid = source.getSystemId() - basehead = os.path.dirname(os.path.normpath(base)) - sysidfilename = os.path.join(basehead, sysid) - if os.path.isfile(sysidfilename): + try: + sysid = source.getSystemId() + basehead = os.path.dirname(os.path.normpath(base)) + encoding = sys.getfilesystemencoding() + if isinstance(sysid, unicode): + if not isinstance(basehead, unicode): + try: + basehead = basehead.decode(encoding) + except UnicodeDecodeError: + sysid = sysid.encode(encoding) + else: + if isinstance(basehead, unicode): + try: + sysid = sysid.decode(encoding) + except UnicodeDecodeError: + basehead = basehead.encode(encoding) + sysidfilename = os.path.join(basehead, sysid) + isfile = os.path.isfile(sysidfilename) + except UnicodeError: + isfile = False + if isfile: source.setSystemId(sysidfilename) f = open(sysidfilename, "rb") else: - source.setSystemId(urlparse.urljoin(base, sysid)) + source.setSystemId(urlparse.urljoin(base, source.getSystemId())) f = urllib.urlopen(source.getSystemId()) source.setByteStream(f) diff --git a/lib-python/2.7/xml/sax/xmlreader.py b/lib-python/2.7/xml/sax/xmlreader.py --- a/lib-python/2.7/xml/sax/xmlreader.py +++ b/lib-python/2.7/xml/sax/xmlreader.py @@ -68,7 +68,7 @@ SAX parsers are not required to provide localization for errors and warnings; if they cannot support the requested locale, - however, they must throw a SAX exception. Applications may + however, they must raise a SAX exception. Applications may request a locale change in the middle of a parse.""" raise SAXNotSupportedException("Locale support not implemented") diff --git a/lib-python/2.7/xmlrpclib.py b/lib-python/2.7/xmlrpclib.py --- a/lib-python/2.7/xmlrpclib.py +++ b/lib-python/2.7/xmlrpclib.py @@ -945,7 +945,7 @@ class MultiCallIterator: """Iterates over the results of a multicall. Exceptions are - thrown in response to xmlrpc faults.""" + raised in response to xmlrpc faults.""" def __init__(self, results): self.results = results diff --git a/lib-python/2.7/zipfile.py b/lib-python/2.7/zipfile.py --- a/lib-python/2.7/zipfile.py +++ b/lib-python/2.7/zipfile.py @@ -5,6 +5,7 @@ import binascii, cStringIO, stat import io import re +import string try: import zlib # We may need its compression method @@ -166,6 +167,8 @@ return endrec data = fpin.read(sizeEndCentDir64Locator) + if len(data) != sizeEndCentDir64Locator: + return endrec sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data) if sig != stringEndArchive64Locator: return endrec @@ -176,6 +179,8 @@ # Assume no 'zip64 extensible data' fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2) data = fpin.read(sizeEndCentDir64) + if len(data) != sizeEndCentDir64: + return endrec sig, sz, create_version, read_version, disk_num, disk_dir, \ dircount, dircount2, dirsize, diroffset = \ struct.unpack(structEndArchive64, data) @@ -211,7 +216,9 @@ except IOError: return None data = fpin.read() - if data[0:4] == stringEndArchive and data[-2:] == "\000\000": + if (len(data) == sizeEndCentDir and + data[0:4] == stringEndArchive and + data[-2:] == b"\000\000"): # the signature is correct and there's no comment, unpack structure endrec = struct.unpack(structEndArchive, data) endrec=list(endrec) @@ -235,6 +242,9 @@ if start >= 0: # found the magic number; attempt to unpack and interpret recData = data[start:start+sizeEndCentDir] + if len(recData) != sizeEndCentDir: + # Zip file is corrupted. + return None endrec = list(struct.unpack(structEndArchive, recData)) commentSize = endrec[_ECD_COMMENT_SIZE] #as claimed by the zip file comment = data[start+sizeEndCentDir:start+sizeEndCentDir+commentSize] @@ -246,7 +256,7 @@ endrec) # Unable to find a valid end of central directory structure - return + return None class ZipInfo (object): @@ -316,7 +326,7 @@ # compress_size Size of the compressed file # file_size Size of the uncompressed file - def FileHeader(self): + def FileHeader(self, zip64=None): """Return the per-file header as a string.""" dt = self.date_time dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] @@ -331,12 +341,17 @@ extra = self.extra - if file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT: - # File is larger than what fits into a 4 byte integer, - # fall back to the ZIP64 extension + if zip64 is None: + zip64 = file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT + if zip64: fmt = ' ZIP64_LIMIT or compress_size > ZIP64_LIMIT: + if not zip64: + raise LargeZipFile("Filesize would require ZIP64 extensions") + # File is larger than what fits into a 4 byte integer, + # fall back to the ZIP64 extension file_size = 0xffffffff compress_size = 0xffffffff self.extract_version = max(45, self.extract_version) @@ -461,6 +476,28 @@ self._UpdateKeys(c) return c + +compressor_names = { + 0: 'store', + 1: 'shrink', + 2: 'reduce', + 3: 'reduce', + 4: 'reduce', + 5: 'reduce', + 6: 'implode', + 7: 'tokenize', + 8: 'deflate', + 9: 'deflate64', + 10: 'implode', + 12: 'bzip2', + 14: 'lzma', + 18: 'terse', + 19: 'lz77', + 97: 'wavpack', + 98: 'ppmd', +} + + class ZipExtFile(io.BufferedIOBase): """File-like object for reading an archive member. Is returned by ZipFile.open(). @@ -475,9 +512,11 @@ # Search for universal newlines or line chunks. PATTERN = re.compile(r'^(?P[^\r\n]+)|(?P\n|\r\n?)') - def __init__(self, fileobj, mode, zipinfo, decrypter=None): + def __init__(self, fileobj, mode, zipinfo, decrypter=None, + close_fileobj=False): self._fileobj = fileobj self._decrypter = decrypter + self._close_fileobj = close_fileobj self._compress_type = zipinfo.compress_type self._compress_size = zipinfo.compress_size @@ -485,6 +524,12 @@ if self._compress_type == ZIP_DEFLATED: self._decompressor = zlib.decompressobj(-15) + elif self._compress_type != ZIP_STORED: + descr = compressor_names.get(self._compress_type) + if descr: + raise NotImplementedError("compression type %d (%s)" % (self._compress_type, descr)) + else: + raise NotImplementedError("compression type %d" % (self._compress_type,)) self._unconsumed = '' self._readbuffer = '' @@ -649,9 +694,15 @@ self._offset += len(data) return data + def close(self): + try : + if self._close_fileobj: + self._fileobj.close() + finally: + super(ZipExtFile, self).close() -class ZipFile: +class ZipFile(object): """ Class with methods to open, read, write, close, list zip files. z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=False) @@ -690,7 +741,7 @@ self.compression = compression # Method of compression self.mode = key = mode.replace('b', '')[0] self.pwd = None - self.comment = '' + self._comment = '' # Check if we were passed a file-like object if isinstance(file, basestring): @@ -710,30 +761,34 @@ self.fp = file self.filename = getattr(file, 'name', None) - if key == 'r': - self._GetContents() - elif key == 'w': - # set the modified flag so central directory gets written - # even if no files are added to the archive - self._didModify = True - elif key == 'a': - try: - # See if file is a zip file + try: + if key == 'r': self._RealGetContents() - # seek to start of directory and overwrite - self.fp.seek(self.start_dir, 0) - except BadZipfile: - # file is not a zip file, just append - self.fp.seek(0, 2) - + elif key == 'w': # set the modified flag so central directory gets written # even if no files are added to the archive self._didModify = True - else: + elif key == 'a': + try: + # See if file is a zip file + self._RealGetContents() + # seek to start of directory and overwrite + self.fp.seek(self.start_dir, 0) + except BadZipfile: + # file is not a zip file, just append + self.fp.seek(0, 2) + + # set the modified flag so central directory gets written + # even if no files are added to the archive + self._didModify = True + else: + raise RuntimeError('Mode must be "r", "w" or "a"') + except: + fp = self.fp + self.fp = None if not self._filePassed: - self.fp.close() - self.fp = None - raise RuntimeError, 'Mode must be "r", "w" or "a"' + fp.close() + raise def __enter__(self): return self @@ -741,17 +796,6 @@ def __exit__(self, type, value, traceback): self.close() - def _GetContents(self): - """Read the directory, making sure we close the file if the format - is bad.""" - try: - self._RealGetContents() - except BadZipfile: - if not self._filePassed: - self.fp.close() - self.fp = None - raise - def _RealGetContents(self): """Read in the table of contents for the ZIP file.""" fp = self.fp @@ -765,7 +809,7 @@ print endrec size_cd = endrec[_ECD_SIZE] # bytes in central directory offset_cd = endrec[_ECD_OFFSET] # offset of central directory - self.comment = endrec[_ECD_COMMENT] # archive comment + self._comment = endrec[_ECD_COMMENT] # archive comment # "concat" is zero, unless zip was concatenated to another file concat = endrec[_ECD_LOCATION] - size_cd - offset_cd @@ -784,9 +828,11 @@ total = 0 while total < size_cd: centdir = fp.read(sizeCentralDir) - if centdir[0:4] != stringCentralDir: - raise BadZipfile, "Bad magic number for central directory" + if len(centdir) != sizeCentralDir: + raise BadZipfile("Truncated central directory") centdir = struct.unpack(structCentralDir, centdir) + if centdir[_CD_SIGNATURE] != stringCentralDir: + raise BadZipfile("Bad magic number for central directory") if self.debug > 2: print centdir filename = fp.read(centdir[_CD_FILENAME_LENGTH]) @@ -845,9 +891,9 @@ try: # Read by chunks, to avoid an OverflowError or a # MemoryError with very large embedded files. - f = self.open(zinfo.filename, "r") - while f.read(chunk_size): # Check CRC-32 - pass + with self.open(zinfo.filename, "r") as f: + while f.read(chunk_size): # Check CRC-32 + pass except BadZipfile: return zinfo.filename @@ -864,6 +910,22 @@ """Set default password for encrypted files.""" self.pwd = pwd + @property + def comment(self): + """The comment text associated with the ZIP file.""" + return self._comment + + @comment.setter + def comment(self, comment): + # check for valid comment length + if len(comment) >= ZIP_MAX_COMMENT: + if self.debug: + print('Archive comment is too long; truncating to %d bytes' + % ZIP_MAX_COMMENT) + comment = comment[:ZIP_MAX_COMMENT] + self._comment = comment + self._didModify = True + def read(self, name, pwd=None): """Return file bytes (as a string) for name.""" return self.open(name, "r", pwd).read() @@ -880,62 +942,72 @@ # given a file object in the constructor if self._filePassed: zef_file = self.fp + should_close = False else: zef_file = open(self.filename, 'rb') + should_close = True - # Make sure we have an info object - if isinstance(name, ZipInfo): - # 'name' is already an info object - zinfo = name - else: - # Get info object for name - zinfo = self.getinfo(name) + try: + # Make sure we have an info object + if isinstance(name, ZipInfo): + # 'name' is already an info object + zinfo = name + else: + # Get info object for name + zinfo = self.getinfo(name) - zef_file.seek(zinfo.header_offset, 0) + zef_file.seek(zinfo.header_offset, 0) - # Skip the file header: - fheader = zef_file.read(sizeFileHeader) - if fheader[0:4] != stringFileHeader: - raise BadZipfile, "Bad magic number for file header" + # Skip the file header: + fheader = zef_file.read(sizeFileHeader) + if len(fheader) != sizeFileHeader: + raise BadZipfile("Truncated file header") + fheader = struct.unpack(structFileHeader, fheader) + if fheader[_FH_SIGNATURE] != stringFileHeader: + raise BadZipfile("Bad magic number for file header") - fheader = struct.unpack(structFileHeader, fheader) - fname = zef_file.read(fheader[_FH_FILENAME_LENGTH]) - if fheader[_FH_EXTRA_FIELD_LENGTH]: - zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH]) + fname = zef_file.read(fheader[_FH_FILENAME_LENGTH]) + if fheader[_FH_EXTRA_FIELD_LENGTH]: + zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH]) - if fname != zinfo.orig_filename: - raise BadZipfile, \ - 'File name in directory "%s" and header "%s" differ.' % ( - zinfo.orig_filename, fname) + if fname != zinfo.orig_filename: + raise BadZipfile, \ + 'File name in directory "%s" and header "%s" differ.' % ( + zinfo.orig_filename, fname) - # check for encrypted flag & handle password - is_encrypted = zinfo.flag_bits & 0x1 - zd = None - if is_encrypted: - if not pwd: - pwd = self.pwd - if not pwd: - raise RuntimeError, "File %s is encrypted, " \ - "password required for extraction" % name + # check for encrypted flag & handle password + is_encrypted = zinfo.flag_bits & 0x1 + zd = None + if is_encrypted: + if not pwd: + pwd = self.pwd + if not pwd: + raise RuntimeError, "File %s is encrypted, " \ + "password required for extraction" % name - zd = _ZipDecrypter(pwd) - # The first 12 bytes in the cypher stream is an encryption header - # used to strengthen the algorithm. The first 11 bytes are - # completely random, while the 12th contains the MSB of the CRC, - # or the MSB of the file time depending on the header type - # and is used to check the correctness of the password. - bytes = zef_file.read(12) - h = map(zd, bytes[0:12]) - if zinfo.flag_bits & 0x8: - # compare against the file type from extended local headers - check_byte = (zinfo._raw_time >> 8) & 0xff - else: - # compare against the CRC otherwise - check_byte = (zinfo.CRC >> 24) & 0xff - if ord(h[11]) != check_byte: - raise RuntimeError("Bad password for file", name) + zd = _ZipDecrypter(pwd) + # The first 12 bytes in the cypher stream is an encryption header + # used to strengthen the algorithm. The first 11 bytes are + # completely random, while the 12th contains the MSB of the CRC, + # or the MSB of the file time depending on the header type + # and is used to check the correctness of the password. + bytes = zef_file.read(12) + h = map(zd, bytes[0:12]) + if zinfo.flag_bits & 0x8: + # compare against the file type from extended local headers + check_byte = (zinfo._raw_time >> 8) & 0xff + else: + # compare against the CRC otherwise + check_byte = (zinfo.CRC >> 24) & 0xff + if ord(h[11]) != check_byte: + raise RuntimeError("Bad password for file", name) - return ZipExtFile(zef_file, mode, zinfo, zd) + return ZipExtFile(zef_file, mode, zinfo, zd, + close_fileobj=should_close) + except: + if should_close: + zef_file.close() + raise def extract(self, member, path=None, pwd=None): """Extract a member from the archive to the current working directory, @@ -969,17 +1041,25 @@ """ # build the destination pathname, replacing # forward slashes to platform specific separators. - # Strip trailing path separator, unless it represents the root. - if (targetpath[-1:] in (os.path.sep, os.path.altsep) - and len(os.path.splitdrive(targetpath)[1]) > 1): - targetpath = targetpath[:-1] + arcname = member.filename.replace('/', os.path.sep) - # don't include leading "/" from file name if present - if member.filename[0] == '/': - targetpath = os.path.join(targetpath, member.filename[1:]) - else: - targetpath = os.path.join(targetpath, member.filename) + if os.path.altsep: + arcname = arcname.replace(os.path.altsep, os.path.sep) + # interpret absolute pathname as relative, remove drive letter or + # UNC path, redundant separators, "." and ".." components. + arcname = os.path.splitdrive(arcname)[1] + arcname = os.path.sep.join(x for x in arcname.split(os.path.sep) + if x not in ('', os.path.curdir, os.path.pardir)) + if os.path.sep == '\\': + # filter illegal characters on Windows + illegal = ':<>|"?*' + table = string.maketrans(illegal, '_' * len(illegal)) + arcname = arcname.translate(table) + # remove trailing dots + arcname = (x.rstrip('.') for x in arcname.split(os.path.sep)) + arcname = os.path.sep.join(x for x in arcname if x) + targetpath = os.path.join(targetpath, arcname) targetpath = os.path.normpath(targetpath) # Create all upper directories if necessary. @@ -992,11 +1072,9 @@ os.mkdir(targetpath) return targetpath - source = self.open(member, pwd=pwd) - target = file(targetpath, "wb") - shutil.copyfileobj(source, target) - source.close() - target.close() + with self.open(member, pwd=pwd) as source, \ + file(targetpath, "wb") as target: + shutil.copyfileobj(source, target) return targetpath @@ -1062,20 +1140,23 @@ zinfo.CRC = 0 self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo - self.fp.write(zinfo.FileHeader()) + self.fp.write(zinfo.FileHeader(False)) return with open(filename, "rb") as fp: # Must overwrite CRC and sizes with correct data later zinfo.CRC = CRC = 0 zinfo.compress_size = compress_size = 0 - zinfo.file_size = file_size = 0 - self.fp.write(zinfo.FileHeader()) + # Compressed size can be larger than uncompressed size + zip64 = self._allowZip64 and \ + zinfo.file_size * 1.05 > ZIP64_LIMIT + self.fp.write(zinfo.FileHeader(zip64)) if zinfo.compress_type == ZIP_DEFLATED: cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) else: cmpr = None + file_size = 0 while 1: buf = fp.read(1024 * 8) if not buf: @@ -1095,11 +1176,16 @@ zinfo.compress_size = file_size zinfo.CRC = CRC zinfo.file_size = file_size - # Seek backwards and write CRC and file sizes + if not zip64 and self._allowZip64: + if file_size > ZIP64_LIMIT: + raise RuntimeError('File size has increased during compressing') + if compress_size > ZIP64_LIMIT: + raise RuntimeError('Compressed size larger than uncompressed size') + # Seek backwards and write file header (which will now include + # correct CRC and file sizes) position = self.fp.tell() # Preserve current position in file - self.fp.seek(zinfo.header_offset + 14, 0) - self.fp.write(struct.pack(" ZIP64_LIMIT or \ + zinfo.compress_size > ZIP64_LIMIT + if zip64 and not self._allowZip64: + raise LargeZipFile("Filesize would require ZIP64 extensions") + self.fp.write(zinfo.FileHeader(zip64)) self.fp.write(bytes) - self.fp.flush() if zinfo.flag_bits & 0x08: # Write CRC and file sizes after the file data - self.fp.write(struct.pack(" ZIP64_LIMIT \ - or zinfo.compress_size > ZIP64_LIMIT: - extra.append(zinfo.file_size) - extra.append(zinfo.compress_size) - file_size = 0xffffffff - compress_size = 0xffffffff - else: - file_size = zinfo.file_size - compress_size = zinfo.compress_size + try: + if self.mode in ("w", "a") and self._didModify: # write ending records + count = 0 + pos1 = self.fp.tell() + for zinfo in self.filelist: # write central directory + count = count + 1 + dt = zinfo.date_time + dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] + dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) + extra = [] + if zinfo.file_size > ZIP64_LIMIT \ + or zinfo.compress_size > ZIP64_LIMIT: + extra.append(zinfo.file_size) + extra.append(zinfo.compress_size) + file_size = 0xffffffff + compress_size = 0xffffffff + else: + file_size = zinfo.file_size + compress_size = zinfo.compress_size - if zinfo.header_offset > ZIP64_LIMIT: - extra.append(zinfo.header_offset) - header_offset = 0xffffffffL - else: - header_offset = zinfo.header_offset + if zinfo.header_offset > ZIP64_LIMIT: + extra.append(zinfo.header_offset) + header_offset = 0xffffffffL + else: + header_offset = zinfo.header_offset - extra_data = zinfo.extra - if extra: - # Append a ZIP64 field to the extra's - extra_data = struct.pack( - '>sys.stderr, (structCentralDir, - stringCentralDir, create_version, - zinfo.create_system, extract_version, zinfo.reserved, - zinfo.flag_bits, zinfo.compress_type, dostime, dosdate, - zinfo.CRC, compress_size, file_size, - len(zinfo.filename), len(extra_data), len(zinfo.comment), - 0, zinfo.internal_attr, zinfo.external_attr, - header_offset) - raise - self.fp.write(centdir) - self.fp.write(filename) - self.fp.write(extra_data) - self.fp.write(zinfo.comment) + try: + filename, flag_bits = zinfo._encodeFilenameFlags() + centdir = struct.pack(structCentralDir, + stringCentralDir, create_version, + zinfo.create_system, extract_version, zinfo.reserved, + flag_bits, zinfo.compress_type, dostime, dosdate, + zinfo.CRC, compress_size, file_size, + len(filename), len(extra_data), len(zinfo.comment), + 0, zinfo.internal_attr, zinfo.external_attr, + header_offset) + except DeprecationWarning: + print >>sys.stderr, (structCentralDir, + stringCentralDir, create_version, + zinfo.create_system, extract_version, zinfo.reserved, + zinfo.flag_bits, zinfo.compress_type, dostime, dosdate, + zinfo.CRC, compress_size, file_size, + len(zinfo.filename), len(extra_data), len(zinfo.comment), + 0, zinfo.internal_attr, zinfo.external_attr, + header_offset) + raise + self.fp.write(centdir) + self.fp.write(filename) + self.fp.write(extra_data) + self.fp.write(zinfo.comment) - pos2 = self.fp.tell() - # Write end-of-zip-archive record - centDirCount = count - centDirSize = pos2 - pos1 - centDirOffset = pos1 - if (centDirCount >= ZIP_FILECOUNT_LIMIT or - centDirOffset > ZIP64_LIMIT or - centDirSize > ZIP64_LIMIT): - # Need to write the ZIP64 end-of-archive records - zip64endrec = struct.pack( - structEndArchive64, stringEndArchive64, - 44, 45, 45, 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset) - self.fp.write(zip64endrec) + pos2 = self.fp.tell() + # Write end-of-zip-archive record + centDirCount = count + centDirSize = pos2 - pos1 + centDirOffset = pos1 + if (centDirCount >= ZIP_FILECOUNT_LIMIT or + centDirOffset > ZIP64_LIMIT or + centDirSize > ZIP64_LIMIT): + # Need to write the ZIP64 end-of-archive records + zip64endrec = struct.pack( + structEndArchive64, stringEndArchive64, + 44, 45, 45, 0, 0, centDirCount, centDirCount, + centDirSize, centDirOffset) + self.fp.write(zip64endrec) - zip64locrec = struct.pack( - structEndArchive64Locator, - stringEndArchive64Locator, 0, pos2, 1) - self.fp.write(zip64locrec) - centDirCount = min(centDirCount, 0xFFFF) - centDirSize = min(centDirSize, 0xFFFFFFFF) - centDirOffset = min(centDirOffset, 0xFFFFFFFF) + zip64locrec = struct.pack( + structEndArchive64Locator, + stringEndArchive64Locator, 0, pos2, 1) + self.fp.write(zip64locrec) + centDirCount = min(centDirCount, 0xFFFF) + centDirSize = min(centDirSize, 0xFFFFFFFF) + centDirOffset = min(centDirOffset, 0xFFFFFFFF) - # check for valid comment length - if len(self.comment) >= ZIP_MAX_COMMENT: - if self.debug > 0: - msg = 'Archive comment is too long; truncating to %d bytes' \ - % ZIP_MAX_COMMENT - self.comment = self.comment[:ZIP_MAX_COMMENT] - - endrec = struct.pack(structEndArchive, stringEndArchive, - 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset, len(self.comment)) - self.fp.write(endrec) - self.fp.write(self.comment) - self.fp.flush() - - if not self._filePassed: - self.fp.close() - self.fp = None + endrec = struct.pack(structEndArchive, stringEndArchive, + 0, 0, centDirCount, centDirCount, + centDirSize, centDirOffset, len(self._comment)) + self.fp.write(endrec) + self.fp.write(self._comment) + self.fp.flush() + finally: + fp = self.fp + self.fp = None + if not self._filePassed: + fp.close() class PyZipFile(ZipFile): @@ -1381,16 +1466,15 @@ if len(args) != 2: print USAGE sys.exit(1) - zf = ZipFile(args[1], 'r') - zf.printdir() - zf.close() + with ZipFile(args[1], 'r') as zf: + zf.printdir() elif args[0] == '-t': if len(args) != 2: print USAGE sys.exit(1) - zf = ZipFile(args[1], 'r') - badfile = zf.testzip() + with ZipFile(args[1], 'r') as zf: + badfile = zf.testzip() if badfile: print("The following enclosed file is corrupted: {!r}".format(badfile)) print "Done testing" @@ -1400,20 +1484,19 @@ print USAGE sys.exit(1) - zf = ZipFile(args[1], 'r') - out = args[2] - for path in zf.namelist(): - if path.startswith('./'): - tgt = os.path.join(out, path[2:]) - else: - tgt = os.path.join(out, path) + with ZipFile(args[1], 'r') as zf: + out = args[2] + for path in zf.namelist(): + if path.startswith('./'): + tgt = os.path.join(out, path[2:]) + else: + tgt = os.path.join(out, path) - tgtdir = os.path.dirname(tgt) - if not os.path.exists(tgtdir): - os.makedirs(tgtdir) - with open(tgt, 'wb') as fp: - fp.write(zf.read(path)) - zf.close() + tgtdir = os.path.dirname(tgt) + if not os.path.exists(tgtdir): + os.makedirs(tgtdir) + with open(tgt, 'wb') as fp: + fp.write(zf.read(path)) elif args[0] == '-c': if len(args) < 3: @@ -1429,11 +1512,9 @@ os.path.join(path, nm), os.path.join(zippath, nm)) # else: ignore - zf = ZipFile(args[1], 'w', allowZip64=True) - for src in args[2:]: - addToZip(zf, src, os.path.basename(src)) - - zf.close() + with ZipFile(args[1], 'w', allowZip64=True) as zf: + for src in args[2:]: + addToZip(zf, src, os.path.basename(src)) if __name__ == "__main__": main() diff --git a/maven/build.xml b/maven/build.xml --- a/maven/build.xml +++ b/maven/build.xml @@ -67,9 +67,13 @@ + + + + - + diff --git a/src/org/python/core/BaseBytes.java b/src/org/python/core/BaseBytes.java --- a/src/org/python/core/BaseBytes.java +++ b/src/org/python/core/BaseBytes.java @@ -9,12 +9,12 @@ import java.util.ListIterator; /** - * Base class for Jython bytearray (and bytes in due course) that provides most of the Java API, - * including Java List behaviour. Attempts to modify the contents through this API will throw a - * TypeError if the actual type of the object is not mutable. - *

- * It is possible for a Java client to treat this class as a List<PyInteger>, obtaining - * equivalent functionality to the Python interface in a Java paradigm. + * Base class for Jython bytearray (and bytes in due course) that provides + * most of the Java API, including Java {@link List} behaviour. Attempts to modify the contents + * through this API will throw a TypeError if the actual type of the object is not + * mutable. It is possible for a Java client to treat this class as a + * List<PyInteger>, obtaining equivalent functionality to the Python interface in a + * Java paradigm. *

* Subclasses must define (from {@link PySequence}): *

    @@ -29,11 +29,29 @@ *
  • {@link #delRange(int, int)}
  • *
* since the default implementations will otherwise throw an exception. + *

+ * Many of the methods implemented here are inherited or thinly wrapped by {@link PyByteArray}, + * which offers them as Java API, or exposes them as Python methods. These prototype Python methods + * mostly accept a {@link PyObject} as argument, where you might have expected a byte[] + * or BaseBytes, in order to accommodate the full range of types accepted by the Python + * equivalent: usually, any PyObject that implements {@link BufferProtocol}, providing + * a one-dimensional array of bytes, is an acceptable argument. In the documentation, the reader + * will often see the terms "byte array" or "object viewable as bytes" instead of + * BaseBytes when this broader scope is intended. + *

+ * Where the methods return a BaseBytes, this is will normally be an instance of the + * class of the object on which the method was actually called. For example {@link #capitalize()}, + * defined in BaseBytes to return a BaseBytes, actually returns a {@link PyByteArray} + * when applied to a bytearray. Or it may be that the method returns a + * PyList of instances of the target type, for example {@link #rpartition(PyObject)}. + * This is achieved by the sub-class defining {@link #getslice(int, int, int)} and + * {@link #getBuilder(int)} to return instances of its own type. See the documentation of particular + * methods for more information. */ public abstract class BaseBytes extends PySequence implements List { /** - * Simple constructor of empty zero-length array of defined type. + * Constructs a zero-length BaseBytes of explicitly-specified sub-type. * * @param type explicit Jython type */ @@ -44,7 +62,7 @@ } /** - * Simple constructor of zero-filled array of defined size and type. + * Constructs a zero-filled array of defined size and type. * * @param size required * @param type explicit Jython type @@ -56,7 +74,7 @@ } /** - * Construct byte array of defined type by copying values from int[]. + * Constructs a byte array of defined type by copying values from int[]. * * @param type explicit Jython type * @param value source of values (and size) @@ -72,8 +90,8 @@ } /** - * Construct byte array of defined type by copying character values from a String. These values - * have to be in the Python byte range 0 to 255. + * Constructs a byte array of defined type by copying character values from a String. These + * values have to be in the Python byte range 0 to 255. * * @param type explicit Jython type * @param value source of characters @@ -250,7 +268,8 @@ * @param encoding name of optional encoding * @param errors name of optional errors policy * @return encoded string - * @throws PyException(TypeError) if the PyString is actually a PyUnicode and encoding is null + * @throws PyException (TypeError) if the PyString is actually a {@link PyUnicode} + * and encoding is null */ protected static String encode(PyString arg, String encoding, String errors) throws PyException { // Jython encode emits a String (not byte[]) @@ -278,7 +297,7 @@ * * @param start index in this byte array at which the first character code lands * @param value source of characters - * @throws PyException(ValueError) if any value[i] > 255 + * @throws PyException (ValueError) if any value[i] > 255 */ protected void setBytes(int start, String value) throws PyException { int n = value.length(); @@ -294,7 +313,7 @@ * * @param start index in this byte array at which the first character code lands * @param value source of characters - * @throws PyException(ValueError) if any value[i] > 255 + * @throws PyException (ValueError) if any value[i] > 255 */ protected void setBytes(int start, int step, String value) throws PyException { int n = value.length(); @@ -307,7 +326,7 @@ /** * Helper for __new__ and __init__ and the Java API constructor from - * int in subclasses. Construct zero-filled bytearray of specified size. + * int in subclasses. Construct zero-filled byte array of specified size. * * @param n size of zero-filled array */ @@ -332,24 +351,11 @@ view.copyTo(storage, offset); } -// /** -// * Helper for the Java API constructor from a {@link #PyBuffer}. View is (perhaps) a stop-gap -// until -// * the Jython implementation of PEP 3118 (buffer API) is embedded. -// * -// * @param value a byte-oriented view -// */ -// void init(PyBuffer value) { -// int n = value.getLen(); -// newStorage(n); -// value.copyTo(storage, offset); -// } -// /** * Helper for __new__ and __init__ and the Java API constructor from - * bytearray or bytes in subclasses. + * bytearray or bytes in subclasses. * - * @param source bytearray (or bytes) to copy + * @param source bytearray (or bytes) to copy */ protected void init(BaseBytes source) { newStorage(source.size); @@ -429,8 +435,8 @@ * Load bytes into the container from the given iterable * * @param iter iterable source of values to enter into the container - * @throws PyException(TypeError) if any value not acceptable type - * @throws PyException(ValueError) if any value<0 or value>255 or string length!=1 + * @throws PyException (TypeError) if any value not acceptable type + * @throws PyException (ValueError) if any value<0 or value>255 or string length!=1 */ void loadFrom(Iterable iter) throws PyException { @@ -535,7 +541,7 @@ * Check that an index is within the range of the array, that is 0<=index<size. * * @param index to check - * @throws PyException(IndexError) if the index is outside the array bounds + * @throws PyException (IndexError) if the index is outside the array bounds */ protected final void indexCheck(int index) throws PyException { if (index < 0 || index >= size) { @@ -569,7 +575,7 @@ * 0..255.) * * @param value to convert. - * @throws PyException(ValueError) if value<0 or value>255 + * @throws PyException (ValueError) if value<0 or value>255 */ protected static final byte byteCheck(int value) throws PyException { if (value < 0 || value > 255) { @@ -584,7 +590,7 @@ * Python bytes run 0..255.) * * @param value to convert. - * @throws PyException(ValueError) if value<0 or value>255 + * @throws PyException (ValueError) if value<0 or value>255 */ protected static final byte byteCheck(PyInteger value) throws PyException { return byteCheck(value.asInt()); @@ -602,8 +608,8 @@ * * * @param value to convert. - * @throws PyException(TypeError) if not acceptable type - * @throws PyException(ValueError) if value<0 or value>255 or string length!=1 + * @throws PyException (TypeError) if not acceptable type + * @throws PyException (ValueError) if value<0 or value>255 or string length!=1 */ protected static final byte byteCheck(PyObject value) throws PyException { if (value.isIndex()) { @@ -627,7 +633,7 @@ * if the return value is not null. * * @param b object to wrap - * @return byte-oriented view or null + * @return byte-oriented view or null */ protected static PyBuffer getView(PyObject b) { @@ -673,6 +679,7 @@ * API for org.python.core.PySequence * ============================================================================================ */ + @Override protected PyInteger pyget(int index) { return new PyInteger(intAt(index)); } @@ -681,8 +688,10 @@ * We're not implementing these here, but we can give a stronger guarantee about the return type * and save some casting and type anxiety. */ + @Override protected abstract BaseBytes getslice(int start, int stop, int step); + @Override protected abstract BaseBytes repeat(int count); /* @@ -696,9 +705,9 @@ * * @param index to insert at * @param element to insert (by value) - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 - * @throws PyException(TypeError) if the subclass is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if the subclass is immutable */ public void pyinsert(int index, PyObject element) { // This won't succeed: it just produces the right error. @@ -720,8 +729,8 @@ } /** - * Class defining the behaviour of bytearray with respect to slice assignment, etc., which - * differs from the default (list) behaviour in small ways. + * Class defining the behaviour of bytearray with respect to slice assignment, + * etc., which differs from the default (list) behaviour in small ways. */ private class IndexDelegate extends PySequence.DefaultIndexDelegate { @@ -886,11 +895,11 @@ } /** - * Implementation of __eq__ (equality) operator, capable of comparison with another byte array - * or bytes. Comparison with an invalid type returns null. + * Implementation of __eq__ (equality) operator, capable of comparison with another byte array. + * Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___eq__(PyObject other) { int cmp = basebytes_cmpeq(other); @@ -904,11 +913,11 @@ } /** - * Implementation of __ne__ (not equals) operator, capable of comparison with another byte array - * or bytes. Comparison with an invalid type returns null. + * Implementation of __ne__ (not equals) operator, capable of comparison with another byte + * array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___ne__(PyObject other) { int cmp = basebytes_cmpeq(other); @@ -922,11 +931,11 @@ } /** - * Implementation of __lt__ (less than) operator, capable of comparison with another byte array - * or bytes. Comparison with an invalid type returns null. + * Implementation of __lt__ (less than) operator, capable of comparison with another byte array. + * Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___lt__(PyObject other) { int cmp = basebytes_cmp(other); @@ -941,10 +950,10 @@ /** * Implementation of __le__ (less than or equal to) operator, capable of comparison with another - * byte array or bytes. Comparison with an invalid type returns null. + * byte array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___le__(PyObject other) { int cmp = basebytes_cmp(other); @@ -959,10 +968,10 @@ /** * Implementation of __ge__ (greater than or equal to) operator, capable of comparison with - * another byte array or bytes. Comparison with an invalid type returns null. + * another byte array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___ge__(PyObject other) { int cmp = basebytes_cmp(other); @@ -977,10 +986,10 @@ /** * Implementation of __gt__ (greater than) operator, capable of comparison with another byte - * array or bytes. Comparison with an invalid type returns null. + * array. Comparison with an invalid type returns null. * * @param other Python object to compare with - * @return Python boolean result or null if not implemented for the other type. + * @return Python boolean result or null if not implemented for the other type. */ final PyObject basebytes___gt__(PyObject other) { int cmp = basebytes_cmp(other); @@ -1031,7 +1040,7 @@ * @param ostart of slice to search. * @param oend of slice to search. * @param endswith true if we are doing endswith, false if startswith. - * @return true if and only if this bytearray ends with (one of) target. + * @return true if and only if this byte array ends with (one of) target. */ protected final synchronized boolean basebytes_starts_or_endswith(PyObject target, PyObject ostart, PyObject oend, boolean endswith) { @@ -1151,7 +1160,7 @@ * error policy. The returned PyObject will usually be a PyUnicode, but in practice * it is whatever the decode method of the codec decides. * - * @param encoding the name of the codec (uses default codec if null) + * @param encoding the name of the codec (uses default codec if null) * @return object containing the decoded characters */ public PyObject decode(String encoding) { @@ -1163,8 +1172,8 @@ * policy. The returned PyObject will usually be a PyUnicode, but in practice it is * whatever the decode method of the codec decides. * - * @param encoding the name of the codec (uses default codec if null) - * @param errors the name of the error policy (uses 'strict' if null) + * @param encoding the name of the codec (uses default codec if null) + * @param errors the name of the error policy (uses 'strict' if null) * @return object containing the decoded characters */ public PyObject decode(String encoding, String errors) { @@ -1210,6 +1219,7 @@ * * @return PyTuple that is first stage in pickling byte array */ + @Override public PyObject __reduce__() { return basebytes___reduce__(); } @@ -1555,6 +1565,7 @@ * the table is only 32 elements long, rather than (as it might be) 256 bytes, the number of * distinct byte values. */ + @Override protected int[] calculateSkipTable() { int[] skipTable = new int[MASK + 1]; int m = pattern.getLen(); @@ -1572,6 +1583,7 @@ * * @return the new effective end of the text */ + @Override public int currIndex() { return right + pattern.getLen() - 1; } @@ -1583,6 +1595,7 @@ * * @return matching index or -1 if no (further) occurrences found */ + @Override public int nextIndex() { int m = pattern.getLen(); @@ -1855,7 +1868,7 @@ * * @param result to receive the decoded values * @param hex specification of the bytes - * @throws PyException(ValueError) if non-hex characters, or isolated ones, are encountered + * @throws PyException (ValueError) if non-hex characters, or isolated ones, are encountered */ static void basebytes_fromhex(BaseBytes result, String hex) throws PyException { @@ -1937,7 +1950,7 @@ // Unsuitable object to be in this join String fmt = "can only join an iterable of bytes (item %d has type '%.80s')"; throw Py.TypeError(String.format(fmt, iterList.size(), o.getType() - .fastGetName())); + .fastGetName())); } iterList.add(v); totalSize += v.getLen(); @@ -1990,6 +2003,9 @@ * the part before the separator, the separator itself, and the part after the separator. If the * separator is not found, return a 3-tuple containing the string itself, followed by two empty * byte arrays. + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2000,6 +2016,9 @@ /** * Ready-to-expose implementation of Python partition(sep). + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2032,6 +2051,9 @@ /** * Construct return value for implementation of Python partition(sep) or * rpartition(sep), returns [0:p], [p:q], [q:] + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param p start of separator * @param q start of tail @@ -2430,6 +2452,9 @@ * containing the part before the separator, the separator itself, and the part after the * separator. If the separator is not found, return a 3-tuple containing two empty byte arrays, * followed by the byte array itself. + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2440,6 +2465,9 @@ /** * Ready-to-expose implementation of Python rpartition(sep). + *

+ * The elements of the PyTuple returned by this method are instances of the same + * actual type as this. * * @param sep the separator on which to partition this byte array * @return a tuple of (head, separator, tail) @@ -2471,6 +2499,9 @@ /** * Implementation of Python rsplit(), that returns a list of the words in the byte * array, using whitespace as the delimiter. See {@link #rsplit(PyObject, int)}. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @return PyList of byte arrays that result from the split */ @@ -2480,10 +2511,13 @@ /** * Implementation of Python rsplit(sep), that returns a list of the words in the - * byte array, using sep as the delimiter. See {@link #rsplit(PyObject, int)} for the semantics - * of the separator. + * byte array, using sep as the delimiter. See {@link #rsplit(PyObject, int)} for + * the semantics of the separator. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @return PyList of byte arrays that result from the split */ public PyList rsplit(PyObject sep) { @@ -2492,22 +2526,26 @@ /** * Implementation of Python rsplit(sep, maxsplit), that returns a list of the words - * in the byte array, using sep as the delimiter. If maxsplit is given, at most maxsplit splits - * are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not - * specified, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep as the delimiter. If maxsplit is + * given, at most maxsplit splits are done (thus, the list will have at most + * maxsplit+1 elements). If maxsplit is not specified, then there is + * no limit on the number of splits (all possible splits are made). *

- * The semantics of sep and maxcount are identical to those of split(sep, maxsplit) - * , except that splits are generated from the right (and pushed onto the front of the result - * list). The result is only different from that of split if maxcount - * limits the number of splits. For example, + * The semantics of sep and maxcount are identical to those of + * split(sep, maxsplit) , except that splits are generated from the right (and + * pushed onto the front of the result list). The result is only different from that of + * split if maxcount limits the number of splits. For example, *

    *
  • bytearray(b' 1 2 3 ').rsplit() returns * [bytearray(b'1'), bytearray(b'2'), bytearray(b'3')], and
  • *
  • bytearray(b' 1 2 3 ').rsplit(None, 1) returns * [bytearray(b' 1 2'), bytearray(b'3')]
  • . *
+ *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2517,10 +2555,13 @@ /** * Ready-to-expose implementation of Python rsplit(sep, maxsplit), that returns a - * list of the words in the byte array, using sep as the delimiter. Use the defines whitespace - * semantics if sep is null. + * list of the words in the byte array, using sep as the delimiter. Use the defines + * whitespace semantics if sep is null. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2534,11 +2575,15 @@ /** * Implementation of Python rsplit(sep, maxsplit), that returns a list of the words - * in the byte array, using sep (which is not null) as the delimiter. If maxsplit>=0, at most - * maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If - * maxsplit<0, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep (which is not null) as the delimiter. + * If maxsplit>=0, at most maxsplit splits are done (thus, the list + * will have at most maxsplit+1 elements). If maxsplit<0, then + * there is no limit on the number of splits (all possible splits are made). + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2589,15 +2634,18 @@ /** * Implementation of Python rsplit(None, maxsplit), that returns a list of the - * words in the byte array, using whitespace as the delimiter. If maxsplit is given, at most - * maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If maxsplit - * is not specified, then there is no limit on the number of splits (all possible splits are - * made). + * words in the byte array, using whitespace as the delimiter. If maxsplit is + * given, at most maxsplit splits are done (thus, the list will have at most + * maxsplit+1 elements). If maxsplit is not specified, then there is no limit on + * the number of splits (all possible splits are made). *

* Runs of consecutive whitespace are regarded as a single separator, and the result will * contain no empty strings at the start or end if the string has leading or trailing * whitespace. Consequently, splitting an empty string or a string consisting of just whitespace - * with a None separator returns []. + * with a None separator returns []. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this/self. * * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split @@ -2648,6 +2696,9 @@ /** * Implementation of Python split(), that returns a list of the words in the byte * array, using whitespace as the delimiter. See {@link #split(PyObject, int)}. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @return PyList of byte arrays that result from the split */ @@ -2657,10 +2708,13 @@ /** * Implementation of Python split(sep), that returns a list of the words in the - * byte array, using sep as the delimiter. See {@link #split(PyObject, int)} for the semantics - * of the separator. + * byte array, using sep as the delimiter. See {@link #split(PyObject, int)} for + * the semantics of the separator. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @return PyList of byte arrays that result from the split */ public PyList split(PyObject sep) { @@ -2669,29 +2723,33 @@ /** * Implementation of Python split(sep, maxsplit), that returns a list of the words - * in the byte array, using sep as the delimiter. If maxsplit is given, at most maxsplit splits - * are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not - * specified, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep as the delimiter. If maxsplit is + * given, at most maxsplit splits are done. (Thus, the list will have at most + * maxsplit+1 elements). If maxsplit is not specified, then there is + * no limit on the number of splits (all possible splits are made). *

- * If sep is given, consecutive delimiters are not grouped together and are deemed to delimit - * empty strings (for example, '1,,2'.split(',') returns ['1', '', '2']). The sep argument may - * consist of multiple characters (for example, '1<>2<>3'.split('<>') returns ['1', - * '2', '3']). Splitting an empty string with a specified separator returns ['']. + * If sep is given, consecutive delimiters are not grouped together and are deemed + * to delimit empty strings (for example, '1,,2'.split(',') returns + * ['1', '', '2']). The sep argument may consist of multiple + * characters (for example, '1<>2<>3'.split('<>') returns ['1', + * '2', '3']). Splitting an empty string with a specified separator ['']. *

- * If sep is not specified or is None, a different splitting algorithm is applied: runs of - * consecutive whitespace are regarded as a single separator, and the result will contain no - * empty strings at the start or end if the string has leading or trailing whitespace. - * Consequently, splitting an empty string or a string consisting of just whitespace with a None - * separator returns []. For example, - * + * If sep is not specified or is None, a different splitting algorithm + * is applied: runs of consecutive whitespace are regarded as a single separator, and the result + * will contain no empty strings at the start or end if the string has leading or trailing + * whitespace. Consequently, splitting an empty string or a string consisting of just whitespace + * with a None separator returns []. For example, *

    *
  • bytearray(b' 1 2 3 ').split() returns * [bytearray(b'1'), bytearray(b'2'), bytearray(b'3')], and
  • *
  • bytearray(b' 1 2 3 ').split(None, 1) returns * [bytearray(b'1'), bytearray(b'2 3 ')].
  • *
+ *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2701,10 +2759,13 @@ /** * Ready-to-expose implementation of Python split(sep, maxsplit), that returns a - * list of the words in the byte array, using sep as the delimiter. Use the defines whitespace - * semantics if sep is null. + * list of the words in the byte array, using sep as the delimiter. Use the defines + * whitespace semantics if sep is null. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2718,11 +2779,15 @@ /** * Implementation of Python split(sep, maxsplit), that returns a list of the words - * in the byte array, using sep (which is not null) as the delimiter. If maxsplit>=0, at most - * maxsplit splits are done (thus, the list will have at most maxsplit+1 elements). If - * maxsplit<0, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using sep (which is not null) as the delimiter. + * If maxsplit>=0, at most maxsplit splits are done (thus, the list + * will have at most maxsplit+1 elements). If maxsplit<0, then + * there is no limit on the number of splits (all possible splits are made). + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * - * @param sep bytes, or object viewable as bytes, defining the separator + * @param sep bytes, or object viewable as bytes, defining the separator * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split */ @@ -2763,14 +2828,18 @@ /** * Implementation of Python split(None, maxsplit), that returns a list of the words - * in the byte array, using whitespace as the delimiter. If maxsplit is given, at most maxsplit - * splits are done (thus, the list will have at most maxsplit+1 elements). If maxsplit is not - * specified, then there is no limit on the number of splits (all possible splits are made). + * in the byte array, using whitespace as the delimiter. If maxsplit is given, at + * most maxsplit splits are done (thus, the list will have at most maxsplit+1 + * elements). If maxsplit is not specified, then there is no limit on the number of + * splits (all possible splits are made). *

* Runs of consecutive whitespace are regarded as a single separator, and the result will * contain no empty strings at the start or end if the string has leading or trailing * whitespace. Consequently, splitting an empty string or a string consisting of just whitespace - * with a None separator returns []. + * with a None separator returns []. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @param maxsplit maximum number of splits * @return PyList of byte arrays that result from the split @@ -2815,6 +2884,9 @@ /** * Implementation of Python splitlines(), returning a list of the lines in the byte * array, breaking at line boundaries. Line breaks are not included in the resulting segments. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @return List of segments */ @@ -2826,6 +2898,9 @@ * Implementation of Python splitlines(keepends), returning a list of the lines in * the string, breaking at line boundaries. Line breaks are not included in the resulting list * unless keepends is true. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @param keepends if true, include the end of line bytes(s) * @return PyList of segments @@ -2838,6 +2913,9 @@ * Ready-to-expose implementation of Python splitlines(keepends), returning a list * of the lines in the array, breaking at line boundaries. Line breaks are not included in the * resulting list unless keepends is given and true. + *

+ * The elements of the PyList returned by this method are instances of the same + * actual type as this. * * @param keepends if true, include the end of line bytes(s) * @return List of segments @@ -2888,7 +2966,7 @@ * byte. * * @param function name - * @param fillchar or null + * @param fillchar or null * @return */ protected static byte fillByteCheck(String function, String fillchar) { @@ -2937,7 +3015,7 @@ * exactly the original object.) * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return (possibly new) byte array containing the result */ final BaseBytes basebytes_center(int width, String fillchar) { @@ -2958,7 +3036,7 @@ * exactly the original object.) * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return (possibly new) byte array containing the result */ final BaseBytes basebytes_ljust(int width, String fillchar) { @@ -2977,7 +3055,7 @@ * exactly the original object.) * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return (possibly new) byte array containing the result */ final BaseBytes basebytes_rjust(int width, String fillchar) { @@ -3323,7 +3401,8 @@ /** * Java API equivalent of Python capitalize(). This method treats the bytes as * Unicode pont codes and is consistent with Java's {@link Character#toUpperCase(char)} and - * {@link Character#toLowerCase(char)}. + * {@link Character#toLowerCase(char)}. The BaseBytes returned by this method has + * the same actual type as this/self. * * @return a copy of the array with its first character capitalized and the rest lowercased. */ @@ -3332,7 +3411,9 @@ } /** - * Ready-to-expose implementation of Python capitalize(). + * Ready-to-expose implementation of Python capitalize(). The + * BaseBytes returned by this method has the same actual type as + * this/self. * * @return a copy of the array with its first character capitalized and the rest lowercased. */ @@ -3365,7 +3446,9 @@ /** * Java API equivalent of Python lower(). This method treats the bytes as Unicode - * pont codes and is consistent with Java's {@link Character#toLowerCase(char)}. + * pont codes and is consistent with Java's {@link Character#toLowerCase(char)}. The + * BaseBytes returned by this method has the same actual type as + * this/self. * * @return a copy of the array with all the cased characters converted to lowercase. */ @@ -3374,7 +3457,8 @@ } /** - * Ready-to-expose implementation of Python lower(). + * Ready-to-expose implementation of Python lower(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a copy of the array with all the cased characters converted to lowercase. */ @@ -3397,7 +3481,8 @@ /** * Java API equivalent of Python swapcase(). This method treats the bytes as * Unicode pont codes and is consistent with Java's {@link Character#toUpperCase(char)} and - * {@link Character#toLowerCase(char)}. + * {@link Character#toLowerCase(char)}. The BaseBytes returned by this method has + * the same actual type as this/self. * * @return a copy of the array with uppercase characters converted to lowercase and vice versa. */ @@ -3406,7 +3491,8 @@ } /** - * Ready-to-expose implementation of Python swapcase(). + * Ready-to-expose implementation of Python swapcase(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a copy of the array with uppercase characters converted to lowercase and vice versa. */ @@ -3432,7 +3518,8 @@ * Java API equivalent of Python title(). The algorithm uses a simple * language-independent definition of a word as groups of consecutive letters. The definition * works in many contexts but it means that apostrophes in contractions and possessives form - * word boundaries, which may not be the desired result. + * word boundaries, which may not be the desired result. The BaseBytes returned by + * this method has the same actual type as this/self. * * @return a titlecased version of the array where words start with an uppercase character and * the remaining characters are lowercase. @@ -3442,7 +3529,8 @@ } /** - * Ready-to-expose implementation of Python title(). + * Ready-to-expose implementation of Python title(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a titlecased version of the array where words start with an uppercase character and * the remaining characters are lowercase. @@ -3481,7 +3569,8 @@ /** * Java API equivalent of Python upper(). Note that * x.upper().isupper() might be false if the array contains uncased - * characters. + * characters. The BaseBytes returned by this method has the same actual type as + * this/self. * * @return a copy of the array with all the cased characters converted to uppercase. */ @@ -3490,7 +3579,8 @@ } /** - * Ready-to-expose implementation of Python upper(). + * Ready-to-expose implementation of Python upper(). The BaseBytes + * returned by this method has the same actual type as this/self. * * @return a copy of the array with all the cased characters converted to uppercase. */ @@ -3533,7 +3623,7 @@ * * @param index of value in byte array * @return the integer value at the index - * @throws PyException(IndexError) if the index is outside the array bounds + * @throws PyException (IndexError) if the index is outside the array bounds */ public synchronized int intAt(int index) throws PyException { indexCheck(index); @@ -3599,7 +3689,7 @@ */ private static final void appendHexEscape(StringBuilder buf, int c) { buf.append("\\x").append(Character.forDigit((c & 0xf0) >> 4, 16)) - .append(Character.forDigit(c & 0xf, 16)); + .append(Character.forDigit(c & 0xf, 16)); } /** @@ -3666,8 +3756,8 @@ */ /** - * Access to the bytearray (or bytes) as a {@link java.util.List}. The List interface supplied - * by BaseBytes delegates to this object. + * Access to the byte array as a {@link java.util.List}. The List interface supplied by + * BaseBytes delegates to this object. */ protected final List listDelegate = new AbstractList() { @@ -3689,9 +3779,9 @@ * Replaces the element at the specified position in this list with the specified element. * * @see java.util.AbstractList#set(int, java.lang.Object) - * @throws PyException(TypeError) if actual class is immutable - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if actual class is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 */ @Override public PyInteger set(int index, PyInteger element) throws PyException { @@ -3707,9 +3797,9 @@ * currently at that position and any subsequent elements to the right. * * @see java.util.AbstractList#add(int, java.lang.Object) - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 - * @throws PyException(TypeError) if the owning concrete subclass is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if the owning concrete subclass is immutable */ @Override public void add(int index, PyInteger element) throws PyException { @@ -3724,7 +3814,7 @@ * removed from the list. * * @see java.util.AbstractList#remove(int) - * @throws PyException(IndexError) if the index is outside the array bounds + * @throws PyException (IndexError) if the index is outside the array bounds */ @Override public PyInteger remove(int index) { @@ -3737,11 +3827,12 @@ }; /** - * Number of bytes in bytearray (or bytes) object. + * Number of bytes in bytearray (or bytes) object. * * @see java.util.List#size() * @return Number of bytes in byte array. * */ + @Override public int size() { return size; } @@ -3749,6 +3840,7 @@ /* * @see java.util.List#isEmpty() */ + @Override public boolean isEmpty() { return size == 0; } @@ -3757,6 +3849,7 @@ * Returns true if this list contains the specified value. More formally, returns true if and * only if this list contains at least one integer e such that o.equals(PyInteger(e)). */ + @Override public boolean contains(Object o) { return listDelegate.contains(o); } @@ -3764,6 +3857,7 @@ /* * @see java.util.List#iterator() */ + @Override public Iterator iterator() { return listDelegate.iterator(); } @@ -3771,6 +3865,7 @@ /* * @see java.util.List#toArray() */ + @Override public Object[] toArray() { return listDelegate.toArray(); } @@ -3778,6 +3873,7 @@ /* * @see java.util.List#toArray(T[]) */ + @Override public T[] toArray(T[] a) { return listDelegate.toArray(a); } @@ -3785,6 +3881,7 @@ /* * @see java.util.List#add(java.lang.Object) */ + @Override public boolean add(PyInteger o) { return listDelegate.add(o); } @@ -3792,6 +3889,7 @@ /* * @see java.util.List#remove(java.lang.Object) */ + @Override public boolean remove(Object o) { return listDelegate.remove(o); } @@ -3799,6 +3897,7 @@ /* * @see java.util.List#containsAll(java.util.Collection) */ + @Override public boolean containsAll(Collection c) { return listDelegate.containsAll(c); } @@ -3806,6 +3905,7 @@ /* * @see java.util.List#addAll(java.util.Collection) */ + @Override public boolean addAll(Collection c) { return listDelegate.addAll(c); } @@ -3813,6 +3913,7 @@ /* * @see java.util.List#addAll(int, java.util.Collection) */ + @Override public boolean addAll(int index, Collection c) { return listDelegate.addAll(index, c); } @@ -3820,6 +3921,7 @@ /* * @see java.util.List#removeAll(java.util.Collection) */ + @Override public boolean removeAll(Collection c) { return listDelegate.removeAll(c); } @@ -3827,6 +3929,7 @@ /* * @see java.util.List#retainAll(java.util.Collection) */ + @Override public boolean retainAll(Collection c) { return listDelegate.retainAll(c); } @@ -3834,6 +3937,7 @@ /* * @see java.util.List#clear() */ + @Override public void clear() { listDelegate.clear(); } @@ -3865,6 +3969,7 @@ /* * @see java.util.List#hashCode() */ + @Override public int hashCode() { return listDelegate.hashCode(); } @@ -3872,6 +3977,7 @@ /* * @see java.util.List#get(int) */ + @Override public PyInteger get(int index) { return listDelegate.get(index); } @@ -3879,6 +3985,7 @@ /* * @see java.util.List#set(int, java.lang.Object) */ + @Override public PyInteger set(int index, PyInteger element) { return listDelegate.set(index, element); } @@ -3886,6 +3993,7 @@ /* * @see java.util.List#add(int, java.lang.Object) */ + @Override public void add(int index, PyInteger element) { listDelegate.add(index, element); } @@ -3893,6 +4001,7 @@ /* * @see java.util.List#remove(int) */ + @Override public PyInteger remove(int index) { return listDelegate.remove(index); } @@ -3900,6 +4009,7 @@ /* * @see java.util.List#indexOf(java.lang.Object) */ + @Override public int indexOf(Object o) { return listDelegate.indexOf(o); } @@ -3907,6 +4017,7 @@ /* * @see java.util.List#lastIndexOf(java.lang.Object) */ + @Override public int lastIndexOf(Object o) { return listDelegate.lastIndexOf(o); } @@ -3914,6 +4025,7 @@ /* * @see java.util.List#listIterator() */ + @Override public ListIterator listIterator() { return listDelegate.listIterator(); } @@ -3921,6 +4033,7 @@ /* * @see java.util.List#listIterator(int) */ + @Override public ListIterator listIterator(int index) { return listDelegate.listIterator(index); } @@ -3928,6 +4041,7 @@ /* * @see java.util.List#subList(int, int) */ + @Override public List subList(int fromIndex, int toIndex) { return listDelegate.subList(fromIndex, toIndex); } @@ -3986,8 +4100,8 @@ * It is intended the client call this method only once to get the result of a series of * append operations. A second call to {@link #getCount()}, before any further appending, * returns a zero-length array. This is to ensure that the same array is not given out - * twice. However, {@link #getCount()} continues to return the number bytes accumuated until - * an append next occurs. + * twice. However, {@link #getCount()} continues to return the number bytes accumulated + * until an append next occurs. * * @return an array containing the accumulated result */ @@ -4131,7 +4245,7 @@ * Every sub-class of BaseBytes overrides this method to return a Builder<B> * where B is (normally) that class's particular type, and it extends * Builder<B> so that {@link Builder#getResult()} produces an instance of - * B from the contents.. + * B from the contents. * * @param capacity of the Builder<B> returned * @return a Builder<B> for the correct sub-class diff --git a/src/org/python/core/BuiltinDocs.java b/src/org/python/core/BuiltinDocs.java --- a/src/org/python/core/BuiltinDocs.java +++ b/src/org/python/core/BuiltinDocs.java @@ -2196,7 +2196,9 @@ "Return a copy of the string S, where all characters occurring\n" + "in the optional argument deletechars are removed, and the\n" + "remaining characters have been mapped through the given\n" + - "translation table, which must be a string of length 256."; + "translation table, which must be a string of length 256.\n" + + "If the table argument is None, no translation is applied and\n" + + "the operation simply removes the characters in deletechars."; public final static String str_upper_doc = "S.upper() -> string\n" + diff --git a/src/org/python/core/PyBUF.java b/src/org/python/core/PyBUF.java --- a/src/org/python/core/PyBUF.java +++ b/src/org/python/core/PyBUF.java @@ -3,20 +3,20 @@ /** * This interface provides a base for the key interface of the buffer API, {@link PyBuffer}, * including symbolic constants used by the consumer of a PyBuffer to specify its - * requirements and assumptions. The Jython buffer API emulates the CPython buffer API. + * requirements and assumptions. The Jython buffer API emulates the CPython buffer API. There are + * two reasons for separating parts of PyBuffer into this interface: *

    - *
  • There are two reasons for separating parts of PyBuffer into this interface: The - * constants defined in CPython have the names PyBUF_SIMPLE, + *
  • The constants defined in CPython have the names PyBUF_SIMPLE, * PyBUF_WRITABLE, etc., and the trick of defining ours here means we can write - * {@link PyBUF#SIMPLE}, {@link PyBUF#WRITABLE}, etc. so source code looks similar.
  • + * PyBUF.SIMPLE, PyBUF.WRITABLE, etc. so source code looks similar. *
  • It is not so easy in Java as it is in C to treat a byte array as storing * anything other than byte, and we prepare for the possibility of buffers with a * series of different primitive types by defining here those methods that would be in common - * between (Byte)Buffer and an assumed future FloatBuffer or + * between (Byte)Buffer and an assumed future FloatBuffer or * TypedBuffer<T>. (Compare java.nio.Buffer.)
  • *
- * Except for other interfaces, it is unlikely any classes would implement PyBUF - * directly. Users of the Jython buffer API can mostly overlook the distinction and just use + * It is unlikely any classes would implement PyBUF, except indirectly through other + * interfaces. Users of the Jython buffer API can mostly overlook the distinction and just use * PyBuffer. */ public interface PyBUF { @@ -39,12 +39,12 @@ /** * An array reporting the size of the buffer, considered as a multidimensional array, in each - * dimension and (by its length) number of dimensions. The size is the size in "items". An item - * is the amount of buffer content addressed by one index or set of indices. In the simplest - * case an item is a single unit (byte), and there is one dimension. In complex cases, the array - * is multi-dimensional, and the item at each location is multi-unit (multi-byte). The consumer - * must not modify this array. A valid shape array is always returned (difference - * from CPython). + * dimension and (by its length) giving the number of dimensions. The size of the buffer is its + * size in "items". An item is the amount of buffer content addressed by one index or set of + * indices. In the simplest case an item is a single unit (byte), and there is one dimension. In + * complex cases, the array is multi-dimensional, and the item at each location is multi-unit + * (multi-byte). The consumer must not modify this array. A valid shape array is + * always returned (difference from CPython). * * @return the dimensions of the buffer as an array */ @@ -59,7 +59,7 @@ /** * The total number of units (bytes) stored, which will be the product of the elements of the - * shape array, and the item size. + * shape array, and the item size in units. * * @return the total number of units stored. */ @@ -81,13 +81,13 @@ /** * The suboffsets array is a further part of the support for interpreting the * buffer as an n-dimensional array of items, where the array potentially uses indirect - * addressing (like a real Java array of arrays, in fact). This is only applicable when there - * are more than 1 dimension and works in conjunction with the strides array. (More + * addressing (like a real Java array of arrays, in fact). This is only applicable when there is + * more than 1 dimension, and it works in conjunction with the strides array. (More * on this in the CPython documentation.) When used, suboffsets[k] is an integer - * index, bit a byte offset as in CPython. The consumer must not modify this array. When not + * index, not a byte offset as in CPython. The consumer must not modify this array. When not * needed for navigation null is returned (as in CPython). * - * @return suboffsets array or null in not necessary for navigation + * @return suboffsets array or null if not necessary for navigation */ int[] getSuboffsets(); @@ -108,15 +108,15 @@ static final int MAX_NDIM = 64; /** * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to - * specify that it expects to write to the buffer contents. getBuffer will raise an exception if - * the exporter's buffer cannot meet this requirement. + * specify that it expects to write to the buffer contents. getBuffer will raise an + * exception if the exporter's buffer cannot meet this requirement. */ static final int WRITABLE = 0x0001; /** * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to * specify that it assumes a simple one-dimensional organisation of the exported storage with - * item size of one. getBuffer will raise an exception if the consumer sets this flag and the - * exporter's buffer cannot be navigated that simply. + * item size of one. getBuffer will raise an exception if the consumer sets this + * flag and the exporter's buffer cannot be navigated that simply. */ static final int SIMPLE = 0; /** @@ -162,7 +162,7 @@ * specify that it will assume a contiguous organisation of the units, but will enquire which * organisation it actually is. * - * getBuffer will raise an exception if the exporter's buffer is not contiguous. + * getBuffer will raise an exception if the exporter's buffer is not contiguous. * ANY_CONTIGUOUS implies STRIDES. */ // Further CPython strangeness since it uses the strides array to answer the enquiry. @@ -216,7 +216,7 @@ /* Constants for readability, not standard for CPython */ /** - * Field mask, use as in if ((flags&NAVIGATION) == STRIDES) .... The importance of + * Field mask, used as in if ((flags&NAVIGATION) == STRIDES) .... The importance of * the subset of flags defined by this mask is not so much in their "navigational" character as * in the way they are treated in a buffer request. *

@@ -243,7 +243,7 @@ */ static final int IS_F_CONTIGUOUS = F_CONTIGUOUS & ~STRIDES; /** - * Field mask, use as in if ((flags&CONTIGUITY)== ... ) .... + * Field mask, used as in if ((flags&CONTIGUITY)== ... ) .... */ static final int CONTIGUITY = (C_CONTIGUOUS | F_CONTIGUOUS | ANY_CONTIGUOUS) & ~STRIDES; -} \ No newline at end of file +} diff --git a/src/org/python/core/PyBuffer.java b/src/org/python/core/PyBuffer.java --- a/src/org/python/core/PyBuffer.java +++ b/src/org/python/core/PyBuffer.java @@ -163,9 +163,9 @@ * When a PyBuffer is the target, the same checks are carried out on the consumer * flags, and a return will normally be a reference to that buffer. A Jython * PyBuffer keeps count of these re-exports in order to match them with the number - * of calls to {@link #release()}. When the last matching release() arrives it is considered - * "final", and release actions may then take place on the exporting object. After the final - * release of a buffer, a call to getBuffer should raise an exception. + * of calls to {@link #release()}. When the last matching release() arrives it is + * considered "final", and release actions may then take place on the exporting object. After + * the final release of a buffer, a call to getBuffer should raise an exception. */ @Override PyBuffer getBuffer(int flags) throws PyException; @@ -213,16 +213,17 @@ * this.getPointer(i). A request for a slice where start = s, * length = N and stride = m, results in a buffer * y such that y(k) = x(s+km) where k=0..(N-1). In Python terms, this is - * the slice x[s : s+(N-1)m+1 : m] (if m>0) or the slice x[s : s+(N-1)m-1 : m] - * (if m<0). Implementations should check that this range is entirely within the current - * buffer. + * the slice x[s : s+(N-1)m+1 : m] (if m>0) or the slice x[s : s+(N-1)m-1 : + * m] (if m<0). Implementations should check that this range is entirely within + * the current buffer. *

* In a simple buffer backed by a contiguous byte array, the result is a strided PyBuffer on the * same storage but where the offset is adjusted by s and the stride is as supplied. If * the current buffer is already strided and/or has an item size larger than single bytes, the - * new start index, length and stride will be translated from the arguments given, through this - * buffer's stride and item size. The consumer always expresses start and - * strides in terms of the abstract view of this buffer. + * new start index, length and stride will be translated + * from the arguments given, through this buffer's stride and item size. The caller always + * expresses start and strides in terms of the abstract view of this + * buffer. * * @param flags specifying features demanded and the navigational capabilities of the consumer * @param start index in the current buffer @@ -265,13 +266,11 @@ * Return a structure describing the slice of a byte array that holds the data being exported to * the consumer. For a one-dimensional contiguous buffer, assuming the following client code * where obj has type BufferProtocol: - * *

      * PyBuffer a = obj.getBuffer();
      * int itemsize = a.getItemsize();
      * PyBuffer.Pointer b = a.getBuf();
      * 
- * * the item with index k is in the array b.storage at index * [b.offset + k*itemsize] to [b.offset + (k+1)*itemsize - 1] * inclusive. And if itemsize==1, the item is simply the byte @@ -287,17 +286,15 @@ PyBuffer.Pointer getBuf(); /** - * Return a structure describing the slice of a byte array that points to a single item from the - * data being exported to the consumer. For a one-dimensional contiguous buffer, assuming the + * Return a structure describing the position in a byte array of a single item from the data + * being exported to the consumer. For a one-dimensional contiguous buffer, assuming the * following client code where obj has type BufferProtocol: - * *
      * int k = ... ;
      * PyBuffer a = obj.getBuffer();
      * int itemsize = a.getItemsize();
      * PyBuffer.Pointer b = a.getPointer(k);
      * 
- * * the item with index k is in the array b.storage at index * [b.offset] to [b.offset + itemsize - 1] inclusive. And if * itemsize==1, the item is simply the byte b.storage[b.offset] @@ -312,11 +309,10 @@ PyBuffer.Pointer getPointer(int index); /** - * Return a structure describing the slice of a byte array that points to a single item from the - * data being exported to the consumer, in the case that array may be multi-dimensional. For a + * Return a structure describing the position in a byte array of a single item from the data + * being exported to the consumer, in the case that array may be multi-dimensional. For a * 3-dimensional contiguous buffer, assuming the following client code where obj * has type BufferProtocol: - * *
      * int i, j, k;
      * // ... calculation that assigns i, j, k
@@ -324,18 +320,17 @@
      * int itemsize = a.getItemsize();
      * PyBuffer.Pointer b = a.getPointer(i,j,k);
      * 
- * * the item with index [i,j,k] is in the array b.storage at index * [b.offset] to [b.offset + itemsize - 1] inclusive. And if * itemsize==1, the item is simply the byte b.storage[b.offset] *

* Essentially this is a method for computing the offset of a particular index. The client is * free to navigate the underlying buffer b.storage without respecting these - * boundaries. - *

- * If the buffer is also non-contiguous, b.storage[b.offset] is still the (first - * byte of) the item at index [0,...,0]. However, it is necessary to navigate b - * using the shape, strides and sub-offsets provided by the API. + * boundaries. If the buffer is non-contiguous, the above description is still valid (since a + * multi-byte item must itself be contiguously stored), but in any additional navigation of + * b.storage[] to other units, the client must use the shape, strides and + * sub-offsets provided by the API. Normally one starts b = a.getBuf() in order to + * establish the offset of index [0,...,0]. * * @param indices multidimensional index at which to position the pointer * @return structure defining the byte[] slice that is the shared data diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java --- a/src/org/python/core/PyByteArray.java +++ b/src/org/python/core/PyByteArray.java @@ -12,26 +12,24 @@ import org.python.expose.MethodType; /** - * Partial implementation of Python bytearray. At the present stage of development, the class - * provides: - *

    - *
  • constructors (both __init__ and the Java API constructors),
  • - *
  • the slice operations (get, set and delete)
  • - *
  • a List<PyInteger> implementation for the Java API
  • - *
- * and this is founded on a particular approach to storage management internally. However, the - * implementation does not support the memoryview interface either for access or a a - * source for its constructors although the signatures are present. The rich set of string-like - * operations due a bytearray is not implemented. - * + * Implementation of Python bytearray with a Java API that includes equivalents to most + * of the Python API. These Python equivalents accept a {@link PyObject} as argument, where you + * might have expected a byte[] or PyByteArray, in order to accommodate + * the full range of types accepted by the Python equivalent: usually, any PyObject + * that implements {@link BufferProtocol}, providing a one-dimensional array of bytes, is an + * acceptable argument. In the documentation, the reader will often see the terms "byte array" or + * "object viewable as bytes" instead of bytearray when this broader scope is intended. + * This may relate to parameters, or to the target object itself (in text that applies equally to + * base or sibling classes). */ @ExposedType(name = "bytearray", base = PyObject.class, doc = BuiltinDocs.bytearray_doc) public class PyByteArray extends BaseBytes implements BufferProtocol { + /** The {@link PyType} of bytearray. */ public static final PyType TYPE = PyType.fromClass(PyByteArray.class); /** - * Create a zero-length Python bytearray of explicitly-specified sub-type + * Constructs a zero-length Python bytearray of explicitly-specified sub-type * * @param type explicit Jython type */ @@ -40,16 +38,16 @@ } /** - * Create a zero-length Python bytearray. + * Constructs a zero-length Python bytearray. */ public PyByteArray() { super(TYPE); } /** - * Create zero-filled Python bytearray of specified size. + * Constructs zero-filled Python bytearray of specified size. * - * @param size of bytearray + * @param size of bytearray */ public PyByteArray(int size) { super(TYPE); @@ -57,7 +55,7 @@ } /** - * Construct bytearray by copying values from int[]. + * Constructs a bytearray by copying values from int[]. * * @param value source of the bytes (and size) */ @@ -66,8 +64,8 @@ } /** - * Create a new array filled exactly by a copy of the contents of the source, which is a - * bytearray (or bytes). + * Constructs a new array filled exactly by a copy of the contents of the source, which is a + * bytearray (or bytes). * * @param value source of the bytes (and size) */ @@ -77,7 +75,7 @@ } /** - * Create a new array filled exactly by a copy of the contents of the source, which is a + * Constructs a new array filled exactly by a copy of the contents of the source, which is a * byte-oriented {@link PyBuffer}. * * @param value source of the bytes (and size) @@ -88,7 +86,7 @@ } /** - * Create a new array filled exactly by a copy of the contents of the source, which is an + * Constructs a new array filled exactly by a copy of the contents of the source, which is an * object supporting the Jython version of the PEP 3118 buffer API. * * @param value source of the bytes (and size) @@ -99,7 +97,7 @@ } /** - * Create a new array filled from an iterable of PyObject. The iterable must yield objects + * Constructs a new array filled from an iterable of PyObject. The iterable must yield objects * convertible to Python bytes (non-negative integers less than 256 or strings of length 1). * * @param value source of the bytes (and size) @@ -110,8 +108,8 @@ } /** - * Create a new array by encoding a PyString argument to bytes. If the PyString is actually a - * PyUnicode, the encoding must be explicitly specified. + * Constructs a new array by encoding a PyString argument to bytes. If the PyString is actually + * a PyUnicode, the encoding must be explicitly specified. * * @param arg primary argument from which value is taken * @param encoding name of optional encoding (must be a string type) @@ -123,12 +121,12 @@ } /** - * Create a new array by encoding a PyString argument to bytes. If the PyString is actually a - * PyUnicode, the encoding must be explicitly specified. + * Constructs a new array by encoding a PyString argument to bytes. If the PyString is actually + * a PyUnicode, the encoding must be explicitly specified. * * @param arg primary argument from which value is taken - * @param encoding name of optional encoding (may be null to select the default for this - * installation) + * @param encoding name of optional encoding (may be null to select the default for + * this installation) * @param errors name of optional errors policy */ public PyByteArray(PyString arg, String encoding, String errors) { @@ -137,8 +135,8 @@ } /** - * Create a new array by encoding a PyString argument to bytes. If the PyString is actually a - * PyUnicode, an exception is thrown saying that the encoding must be explicitly specified. + * Constructs a new array by encoding a PyString argument to bytes. If the PyString is actually + * a PyUnicode, an exception is thrown saying that the encoding must be explicitly specified. * * @param arg primary argument from which value is taken */ @@ -148,7 +146,8 @@ } /** - * Construct bytearray by re-using an array of byte as storage initialised by the client. + * Constructs a bytearray by re-using an array of byte as storage initialised by + * the client. * * @param storage pre-initialised with desired value: the caller should not keep a reference */ @@ -158,12 +157,13 @@ } /** - * Construct bytearray by re-using an array of byte as storage initialised by the client. + * Constructs a bytearray by re-using an array of byte as storage initialised by + * the client. * * @param storage pre-initialised with desired value: the caller should not keep a reference * @param size number of bytes actually used - * @throws IllegalArgumentException if the range [0:size] is not within the array bounds of - * the storage. + * @throws IllegalArgumentException if the range [0:size] is not within the array bounds of the + * storage. */ PyByteArray(byte[] storage, int size) { super(TYPE); @@ -171,28 +171,26 @@ } /** - * Create a new bytearray object from an arbitrary Python object according to the same rules as - * apply in Python to the bytearray() constructor: + * Constructs a new bytearray object from an arbitrary Python object according to + * the same rules as apply in Python to the bytearray() constructor: *
    - *
  • bytearray() Construct a zero-length bytearray (arg is null).
  • - *
  • bytearray(int) Construct a zero-initialized bytearray of the given length.
  • - *
  • bytearray(iterable_of_ints) Construct from iterable yielding integers in [0..255]
  • - *
  • bytearray(string [, encoding [, errors] ]) Construct from a text string, optionally using - * the specified encoding.
  • - *
  • bytearray(unicode, encoding [, errors]) Construct from a unicode string using the - * specified encoding.
  • - *
  • bytearray(bytes_or_bytearray) Construct as a mutable copy of bytes or existing bytearray - * object.
  • + *
  • bytearray() Construct a zero-length bytearray.
  • + *
  • bytearray(int) Construct a zero-initialized bytearray of the + * given length.
  • + *
  • bytearray(iterable_of_ints) Construct from iterable yielding integers in + * [0..255]
  • + *
  • bytearray(buffer) Construct by reading from any object implementing + * {@link BufferProtocol}, including str/bytes or another bytearray.
  • *
* When it is necessary to specify an encoding, as in the Python signature - * bytearray(string, encoding[, errors]), use the constructor - * {@link #PyByteArray(PyString, String, String)}. If the PyString is actually a PyUnicode, an - * encoding must be specified, and using this constructor will throw an exception about that. + * bytearray(string, encoding [, errors]), use the constructor + * {@link #PyByteArray(PyString, String, String)}. If the PyString is actually a + * PyUnicode, an encoding must be specified, and using this constructor will throw + * an exception about that. * - * @param arg primary argument from which value is taken (may be null) - * @throws PyException in the same circumstances as bytearray(arg), TypeError for non-iterable, - * non-integer argument type, and ValueError if iterables do not yield byte [0..255] - * values. + * @param arg primary argument from which value is taken (may be null) + * @throws PyException (TypeError) for non-iterable, + * @throws PyException (ValueError) if iterables do not yield byte [0..255] values. */ public PyByteArray(PyObject arg) throws PyException { super(TYPE); @@ -284,18 +282,19 @@ } } - /* ============================================================================================ + /* + * ============================================================================================ * API for org.python.core.PySequence * ============================================================================================ */ /** - * Returns a slice of elements from this sequence as a PyByteArray. + * Returns a slice of elements from this sequence as a PyByteArray. * * @param start the position of the first element. * @param stop one more than the position of the last element. * @param step the step size. - * @return a PyByteArray corresponding the the given range of elements. + * @return a PyByteArray corresponding the the given range of elements. */ @Override protected synchronized PyByteArray getslice(int start, int stop, int step) { @@ -333,8 +332,8 @@ } /** - * Returns a PyByteArray that repeats this sequence the given number of times, as in the - * implementation of __mul__ for strings. + * Returns a PyByteArray that repeats this sequence the given number of times, as + * in the implementation of __mul__ for strings. * * @param count the number of times to repeat this. * @return this byte array repeated count times. @@ -347,8 +346,8 @@ } /** - * Replace the contents of this PyByteArray with the given number of repeats of the original - * contents, as in the implementation of __mul__ for strings. + * Replace the contents of this PyByteArray with the given number of repeats of the + * original contents, as in the implementation of __mul__ for strings. * * @param count the number of times to repeat this. */ @@ -357,35 +356,36 @@ } /** - * Sets the indexed element of the bytearray to the given value. This is an extension point - * called by PySequence in its implementation of {@link #__setitem__} It is guaranteed by - * PySequence that the index is within the bounds of the array. Any other clients calling - * pyset(int) must make the same guarantee. + * Sets the indexed element of the bytearray to the given value. This is an + * extension point called by PySequence in its implementation of {@link #__setitem__} It is + * guaranteed by PySequence that the index is within the bounds of the array. Any other clients + * calling pyset(int) must make the same guarantee. * * @param index index of the element to set. * @param value the value to set this element to. - * @throws PyException(AttributeError) if value cannot be converted to an integer - * @throws PyException(ValueError) if value<0 or value>255 + * @throws PyException (AttributeError) if value cannot be converted to an integer + * @throws PyException (ValueError) if value<0 or value>255 */ + @Override public synchronized void pyset(int index, PyObject value) throws PyException { storage[index + offset] = byteCheck(value); } /** - * Insert the element (interpreted as a Python byte value) at the given index. - * Python int, long and string types of length 1 are allowed. + * Insert the element (interpreted as a Python byte value) at the given index. Python + * int, long and str types of length 1 are allowed. * * @param index to insert at * @param element to insert (by value) - * @throws PyException(IndexError) if the index is outside the array bounds - * @throws PyException(ValueError) if element<0 or element>255 - * @throws PyException(TypeError) if the subclass is immutable + * @throws PyException (IndexError) if the index is outside the array bounds + * @throws PyException (ValueError) if element<0 or element>255 + * @throws PyException (TypeError) if the subclass is immutable */ @Override public synchronized void pyinsert(int index, PyObject element) { // Open a space at the right location. storageReplace(index, 0, 1); - storage[offset+index] = byteCheck(element); + storage[offset + index] = byteCheck(element); } /** @@ -398,9 +398,10 @@ * of exactly that many elements (or convertible to such a sequence). *

* When assigning from a sequence type or iterator, the sequence may contain arbitrary - * PyObjects, but acceptable ones are PyInteger, PyLong or PyString of length 1. If - * any one of them proves unsuitable for assignment to a Python bytarray element, an exception - * is thrown and this bytearray is unchanged. + * {@link PyObject}s, but acceptable ones are {@link PyInteger}, {@link PyLong} or + * {@link PyString} of length 1. If any one of them proves unsuitable for assignment to a Python + * bytearray element, an exception is thrown and this bytearray is + * unchanged. * *

      * a = bytearray(b'abcdefghijklmnopqrst')
@@ -472,14 +473,14 @@
 
     /**
      * Sets the given range of elements according to Python slice assignment semantics from a
-     * zero-filled bytearray of the given length.
+     * zero-filled bytearray of the given length.
      *
      * @see #setslice(int, int, int, PyObject)
      * @param start the position of the first element.
      * @param stop one more than the position of the last element.
      * @param step the step size.
      * @param len number of zeros to insert consistent with the slice assignment
-     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
+     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
      */
     private void setslice(int start, int stop, int step, int len) throws PyException {
         if (step == 1) {
@@ -501,29 +502,36 @@
 
     /**
      * Sets the given range of elements according to Python slice assignment semantics from a
-     * PyString.
+     * {@link PyString} that is not a {@link PyUnicode}.
      *
      * @see #setslice(int, int, int, PyObject)
      * @param start the position of the first element.
      * @param stop one more than the position of the last element.
      * @param step the step size.
      * @param value a PyString object consistent with the slice assignment
-     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
+     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
+     * @throws PyException (ValueError) if the value is a PyUnicode
      */
     private void setslice(int start, int stop, int step, PyString value) throws PyException {
-        String v = value.asString();
-        int len = v.length();
-        if (step == 1) {
-            // Delete this[start:stop] and open a space of the right size
-            storageReplace(start, stop - start, len);
-            setBytes(start, v);
+        if (value instanceof PyUnicode) {
+            // Has to be 8-bit PyString
+            throw Py.TypeError("can't set bytearray slice from unicode");
         } else {
-            // This is an extended slice which means we are replacing elements
-            int n = sliceLength(start, stop, step);
-            if (n != len) {
-                throw SliceSizeError("bytes", len, n);
+            // Assignment is from 8-bit data
+            String v = value.asString();
+            int len = v.length();
+            if (step == 1) {
+                // Delete this[start:stop] and open a space of the right size
+                storageReplace(start, stop - start, len);
+                setBytes(start, v);
+            } else {
+                // This is an extended slice which means we are replacing elements
+                int n = sliceLength(start, stop, step);
+                if (n != len) {
+                    throw SliceSizeError("bytes", len, n);
+                }
+                setBytes(start, step, v);
             }
-            setBytes(start, step, v);
         }
     }
 
@@ -536,7 +544,7 @@
      * @param stop one more than the position of the last element.
      * @param step the step size.
      * @param value an object supporting the buffer API consistent with the slice assignment
-     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
+     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
      */
     private void setslice(int start, int stop, int step, BufferProtocol value) throws PyException {
 
@@ -547,7 +555,7 @@
         if (step == 1) {
             // Delete this[start:stop] and open a space of the right size
             storageReplace(start, stop - start, len);
-            view.copyTo(storage, start+offset);
+            view.copyTo(storage, start + offset);
 
         } else {
             // This is an extended slice which means we are replacing elements
@@ -564,14 +572,14 @@
 
     /**
      * Sets the given range of elements according to Python slice assignment semantics from a
-     * bytearray (or bytes).
+     * bytearray (or bytes).
      *
      * @see #setslice(int, int, int, PyObject)
      * @param start the position of the first element.
      * @param stop one more than the position of the last element.
      * @param step the step size.
-     * @param value a bytearray (or bytes) object consistent with the slice assignment
-     * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
+     * @param value a bytearray (or bytes) object consistent with the slice assignment
+     * @throws PyException (SliceSizeError) if the value size is inconsistent with an extended slice
      */
     private void setslice(int start, int stop, int step, BaseBytes value) throws PyException {
 
@@ -603,14 +611,14 @@
 
     /**
      * Sets the given range of elements according to Python slice assignment semantics from a
-     * bytearray (or bytes).
+     * bytearray (or bytes).
      *
      * @see #setslice(int, int, int, PyObject)
      * @param start the position of the first element.
      * @param stop one more than the position of the last element.
      * @param step the step size.
      * @param iter iterable source of values to enter in the array
-     * @throws PyException(SliceSizeError) if the iterable size is inconsistent with an extended
+     * @throws PyException (SliceSizeError) if the iterable size is inconsistent with an extended
      *             slice
      */
     private void setslice(int start, int stop, int step, Iterable iter) {
@@ -640,21 +648,12 @@
         }
     }
 
-// Idiom:
-// if (step == 1) {
-// // Do something efficient with block start...stop-1
-// } else {
-// int n = sliceLength(start, stop, step);
-// for (int i = start, j = 0; j < n; i += step, j++) {
-// // Perform jth operation with element i
-// }
-// }
-
     /*
      * Deletes an element from the sequence (and closes up the gap).
      *
      * @param index index of the element to delete.
      */
+    @Override
     protected synchronized void del(int index) {
         // XXX Change SequenceIndexDelegate to avoid repeated calls to del(int) for extended slice
         storageDelete(index, 1);
@@ -667,6 +666,7 @@
      *
      * @param stop one more than the position of the last element.
      */
+    @Override
     protected synchronized void delRange(int start, int stop) {
         storageDelete(start, stop - start);
     }
@@ -714,29 +714,29 @@
     }
 
     /**
-     * Initialise a mutable bytearray object from various arguments. This single initialisation must
-     * support:
+     * Initialise a mutable bytearray object from various arguments. This single
+     * initialisation must support:
      * 
    - *
  • bytearray() Construct a zero-length bytearray.
  • - *
  • bytearray(int) Construct a zero-initialized bytearray of the given length.
  • - *
  • bytearray(iterable_of_ints) Construct from iterable yielding integers in [0..255]
  • - *
  • bytearray(string [, encoding [, errors] ]) Construct from a text string, optionally using + *
  • bytearray() Construct a zero-length bytearray.
  • + *
  • bytearray(int) Construct a zero-initialized bytearray of the + * given length.
  • + *
  • bytearray(iterable_of_ints) Construct from iterable yielding integers in + * [0..255]
  • + *
  • bytearray(buffer) Construct by reading from any object implementing + * {@link BufferProtocol}, including str/bytes or another bytearray.
  • + *
  • bytearray(string, encoding [, errors]) Construct from a + * str/bytes, decoded using the system default encoding, and encoded to bytes using * the specified encoding.
  • - *
  • bytearray(unicode, encoding [, errors]) Construct from a unicode string using the - * specified encoding.
  • - *
  • bytearray(bytes_or_bytearray) Construct as a mutable copy of bytes or existing bytearray - * object.
  • + *
  • bytearray(unicode, encoding [, errors]) Construct from a + * unicode string, encoded to bytes using the specified encoding.
  • *
- * Unlike CPython we are not able to support the initialisation:
  • bytearray(memory_view) - * Construct as copy of any object implementing the buffer API.
  • Although effectively - * a constructor, it is possible to call __init__ on a 'used' object so the method does not - * assume any particular prior state. + * Although effectively a constructor, it is possible to call __init__ on a 'used' + * object so the method does not assume any particular prior state. * * @param args argument array according to Jython conventions * @param kwds Keywords according to Jython conventions - * @throws PyException in the same circumstances as bytearray(arg), TypeError for non-iterable, - * non-integer argument type, and ValueError if iterables do not yield byte [0..255] - * values. + * @throws PyException (TypeError) for non-iterable, + * @throws PyException (ValueError) if iterables do not yield byte [0..255] values. */ @ExposedNew @ExposedMethod(doc = BuiltinDocs.bytearray___init___doc) @@ -795,7 +795,8 @@ }; } - /* ============================================================================================ + /* + * ============================================================================================ * Python API rich comparison operations * ============================================================================================ */ @@ -830,8 +831,6 @@ return basebytes___gt__(other); } - - @ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.bytearray___eq___doc) final synchronized PyObject bytearray___eq__(PyObject other) { return basebytes___eq__(other); @@ -862,7 +861,8 @@ return basebytes___gt__(other); } -/* ============================================================================================ +/* + * ============================================================================================ * Python API for bytearray * ============================================================================================ */ @@ -876,10 +876,8 @@ final synchronized PyObject bytearray___add__(PyObject o) { PyByteArray sum = null; - // XXX re-write using buffer API - if (o instanceof BaseBytes) { BaseBytes ob = (BaseBytes)o; // Quick route: allocate the right size bytearray and copy the two parts in. @@ -985,7 +983,7 @@ * length 1. * * @param element the item to append. - * @throws PyException(ValueError) if element<0 or element>255 + * @throws PyException (ValueError) if element<0 or element>255 */ public void append(PyObject element) { bytearray_append(element); @@ -1002,9 +1000,10 @@ * Implement to the standard Python __contains__ method, which in turn implements the * in operator. * - * @param o the element to search for in this bytearray. + * @param o the element to search for in this bytearray. * @return the result of the search. **/ + @Override public boolean __contains__(PyObject o) { return basebytes___contains__(o); } @@ -1038,7 +1037,7 @@ * width is less than this.size(). * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return new byte array containing the result */ public PyByteArray center(int width, String fillchar) { @@ -1097,11 +1096,11 @@ * Implementation of Python endswith(suffix). * * When suffix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray ends with the suffix. - * suffix can also be a tuple of suffixes to look for. + * true if and only if this bytearray ends with the + * suffix. suffix can also be a tuple of suffixes to look for. * * @param suffix byte array to match, or object viewable as such, or a tuple of them - * @return true if and only if this bytearray ends with the suffix (or one of them) + * @return true if and only if this bytearray ends with the suffix (or one of them) */ public boolean endswith(PyObject suffix) { return basebytes_starts_or_endswith(suffix, null, null, true); @@ -1111,13 +1110,14 @@ * Implementation of Python endswith( suffix [, start ] ). * * When suffix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray ends with the suffix. - * suffix can also be a tuple of suffixes to look for. With optional - * start (which may be null or Py.None), define the - * effective bytearray to be the slice [start:] of this bytearray. + * true if and only if this bytearray ends with the + * suffix. suffix can also be a tuple of suffixes to look for. With + * optional start (which may be null or Py.None), define + * the effective bytearray to be the slice [start:] of this + * bytearray. * * @param suffix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match + * @param start of slice in this bytearray to match * @return true if and only if this[start:] ends with the suffix (or one of them) */ public boolean endswith(PyObject suffix, PyObject start) { @@ -1128,15 +1128,15 @@ * Implementation of Python endswith( suffix [, start [, end ]] ). * * When suffix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray ends with the suffix. - * suffix can also be a tuple of suffixes to look for. With optional - * start and end (which may be null or - * Py.None), define the effective bytearray to be the slice - * [start:end] of this bytearray. + * true if and only if this bytearray ends with the + * suffix. suffix can also be a tuple of suffixes to look for. With + * optional start and end (which may be null or + * Py.None), define the effective bytearray to be the slice + * [start:end] of this bytearray. * * @param suffix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match - * @param end of slice in this bytearray to match + * @param start of slice in this bytearray to match + * @param end of slice in this bytearray to match * @return true if and only if this[start:end] ends with the suffix (or one of them) */ public boolean endswith(PyObject suffix, PyObject start, PyObject end) { @@ -1180,7 +1180,7 @@ /** * Append the elements in the argument sequence to the end of the array, equivalent to: - * s[len(s):len(s)] = o. The argument must be a subclass of BaseBytes or an + * s[len(s):len(s)] = o. The argument must be a subclass of {@link BaseBytes} or an * iterable type returning elements compatible with byte assignment. * * @param o the sequence of items to append to the list. @@ -1192,7 +1192,7 @@ @ExposedMethod(doc = BuiltinDocs.bytearray_extend_doc) final synchronized void bytearray_extend(PyObject o) { // Use the general method, assigning to the crack at the end of the array. - // Note this deals with all legitimate PyObject types and the case o==this. + // Note this deals with all legitimate PyObject types including the case o==this. setslice(size, size, 1, o); } @@ -1253,7 +1253,7 @@ *
    * * @param hex specification of the bytes - * @throws PyException(ValueError) if non-hex characters, or isolated ones, are encountered + * @throws PyException (ValueError) if non-hex characters, or isolated ones, are encountered */ static PyByteArray fromhex(String hex) throws PyException { return bytearray_fromhex(TYPE, hex); @@ -1322,9 +1322,11 @@ /** * This type is not hashable. + * + * @throws PyException (TypeError) as this type is not hashable. */ @Override - public int hashCode() { + public int hashCode() throws PyException { return bytearray___hash__(); } @@ -1443,10 +1445,10 @@ return (PyByteArray)basebytes_upper(); } - /** - * Implementation of Python join(iterable). Return a bytearray which is the - * concatenation of the byte arrays in the iterable iterable. The separator between - * elements is the byte array providing this method. + /** + * Implementation of Python join(iterable). Return a bytearray which + * is the concatenation of the byte arrays in the iterable iterable. The separator + * between elements is the byte array providing this method. * * @param iterable of byte array objects, or objects viewable as such. * @return byte array produced by concatenation. @@ -1484,7 +1486,7 @@ * width is less than this.size(). * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return new byte array containing the result */ public PyByteArray ljust(int width, String fillchar) { @@ -1498,8 +1500,8 @@ } /** - * Implementation of Python lstrip(). Return a copy of the byte array with the leading - * whitespace characters removed. + * Implementation of Python lstrip(). Return a copy of the byte array with the + * leading whitespace characters removed. * * @return a byte array containing this value stripped of those bytes */ @@ -1510,10 +1512,10 @@ /** * Implementation of Python lstrip(bytes) * - * Return a copy of the byte array with the leading characters removed. The bytes - * argument is an object specifying the set of characters to be removed. If null or None, the - * bytes argument defaults to removing whitespace. The bytes argument is not a prefix; - * rather, all combinations of its values are stripped. + * Return a copy of the byte array with the leading characters removed. The bytes argument is an + * object specifying the set of characters to be removed. If null or + * None, the bytes argument defaults to removing whitespace. The bytes argument is + * not a prefix; rather, all combinations of its values are stripped. * * @param bytes treated as a set of bytes defining what values to strip * @return a byte array containing this value stripped of those bytes (at the left) @@ -1542,7 +1544,8 @@ } /** - * Removes and return the last element in the byte array. + * Remove and return the last element in the byte array. + * * @return PyInteger representing the value */ public PyInteger pop() { @@ -1584,7 +1587,7 @@ * argument must be a PyInteger, PyLong or string of length 1. * * @param o the value to remove from the list. - * @throws PyException ValueError if o not found in bytearray + * @throws PyException ValueError if o not found in bytearray */ public void remove(PyObject o) throws PyException { bytearray_remove(o); @@ -1746,7 +1749,7 @@ * width is less than this.size(). * * @param width desired - * @param fillchar one-byte String to fill with, or null implying space + * @param fillchar one-byte String to fill with, or null implying space * @return new byte array containing the result */ public PyByteArray rjust(int width, String fillchar) { @@ -1793,7 +1796,8 @@ } /** - * Implementation of Python rstrip(). Return a copy of the byte array with the trailing whitespace characters removed. + * Implementation of Python rstrip(). Return a copy of the byte array with the + * trailing whitespace characters removed. * * @return a byte array containing this value stripped of those bytes (at right) */ @@ -1804,10 +1808,10 @@ /** * Implementation of Python rstrip(bytes) * - * Return a copy of the byte array with the trailing characters removed. The bytes - * argument is an object specifying the set of characters to be removed. If null or None, the - * bytes argument defaults to removing whitespace. The bytes argument is not a suffix; - * rather, all combinations of its values are stripped. + * Return a copy of the byte array with the trailing characters removed. The bytes argument is + * an object specifying the set of characters to be removed. If null or + * None, the bytes argument defaults to removing whitespace. The bytes argument is + * not a suffix; rather, all combinations of its values are stripped. * * @param bytes treated as a set of bytes defining what values to strip * @return a byte array containing this value stripped of those bytes (at right) @@ -1844,11 +1848,12 @@ * Implementation of Python startswith(prefix). * * When prefix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray starts with the prefix. - * prefix can also be a tuple of prefixes to look for. + * true if and only if this bytearray starts with the + * prefix. prefix can also be a tuple of prefixes to look for. * * @param prefix byte array to match, or object viewable as such, or a tuple of them - * @return true if and only if this bytearray starts with the prefix (or one of them) + * @return true if and only if this bytearray starts with the prefix (or one of + * them) */ public boolean startswith(PyObject prefix) { return basebytes_starts_or_endswith(prefix, null, null, false); @@ -1858,13 +1863,14 @@ * Implementation of Python startswith( prefix [, start ] ). * * When prefix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray starts with the prefix. - * prefix can also be a tuple of prefixes to look for. With optional - * start (which may be null or Py.None), define the - * effective bytearray to be the slice [start:] of this bytearray. + * true if and only if this bytearray starts with the + * prefix. prefix can also be a tuple of prefixes to look for. With + * optional start (which may be null or Py.None), define + * the effective bytearray to be the slice [start:] of this + * bytearray. * * @param prefix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match + * @param start of slice in this bytearray to match * @return true if and only if this[start:] starts with the prefix (or one of them) */ public boolean startswith(PyObject prefix, PyObject start) { @@ -1875,15 +1881,15 @@ * Implementation of Python startswith( prefix [, start [, end ]] ). * * When prefix is of a type that may be treated as an array of bytes, return - * true if and only if this bytearray starts with the prefix. - * prefix can also be a tuple of prefixes to look for. With optional - * start and end (which may be null or - * Py.None), define the effective bytearray to be the slice - * [start:end] of this bytearray. + * true if and only if this bytearray starts with the + * prefix. prefix can also be a tuple of prefixes to look for. With + * optional start and end (which may be null or + * Py.None), define the effective bytearray to be the slice + * [start:end] of this bytearray. * * @param prefix byte array to match, or object viewable as such, or a tuple of them - * @param start of slice in this bytearray to match - * @param end of slice in this bytearray to match + * @param start of slice in this bytearray to match + * @param end of slice in this bytearray to match * @return true if and only if this[start:end] starts with the prefix (or one of them) */ public boolean startswith(PyObject prefix, PyObject start, PyObject end) { @@ -1896,8 +1902,8 @@ } /** - * Implementation of Python strip(). Return a copy of the byte array with the leading - * and trailing whitespace characters removed. + * Implementation of Python strip(). Return a copy of the byte array with the + * leading and trailing whitespace characters removed. * * @return a byte array containing this value stripped of those bytes (left and right) */ @@ -1909,9 +1915,10 @@ * Implementation of Python strip(bytes) * * Return a copy of the byte array with the leading and trailing characters removed. The bytes - * argument is anbyte arrayt specifying the set of characters to be removed. If null or None, the - * bytes argument defaults to removing whitespace. The bytes argument is not a prefix or suffix; - * rather, all combinations of its values are stripped. + * argument is anbyte arrayt specifying the set of characters to be removed. If + * null or None, the bytes argument defaults to removing whitespace. + * The bytes argument is not a prefix or suffix; rather, all combinations of its values are + * stripped. * * @param bytes treated as a set of bytes defining what values to strip * @return a byte array containing this value stripped of those bytes (left and right) @@ -1980,13 +1987,13 @@ * Implementation of Python translate(table). * * Return a copy of the byte array where all bytes occurring in the optional argument - * deletechars are removed, and the remaining bytes have been mapped through the given - * translation table, which must be of length 256. + * deletechars are removed, and the remaining bytes have been mapped through the + * given translation table, which must be of length 256. * * @param table length 256 translation table (of a type that may be regarded as a byte array) * @return translated byte array */ - public PyByteArray translate(PyObject table) { + public PyByteArray translate(PyObject table) { return bytearray_translate(table, null); } @@ -1994,12 +2001,12 @@ * Implementation of Python translate(table[, deletechars]). * * Return a copy of the byte array where all bytes occurring in the optional argument - * deletechars are removed, and the remaining bytes have been mapped through the given - * translation table, which must be of length 256. + * deletechars are removed, and the remaining bytes have been mapped through the + * given translation table, which must be of length 256. * - * You can use the maketrans() helper function in the string module to create a translation - * table. For string objects, set the table argument to None for translations that only delete - * characters: + * You can use the Python maketrans() helper function in the string + * module to create a translation table. For string objects, set the table argument to + * None for translations that only delete characters: * * @param table length 256 translation table (of a type that may be regarded as a byte array) * @param deletechars object that may be regarded as a byte array, defining bytes to delete @@ -2131,6 +2138,7 @@ * * @param needed becomes the new value of this.size */ + @Override protected void newStorage(int needed) { if (needed > 0) { final int L = recLength(needed); @@ -2168,7 +2176,7 @@ * been preserved, although probably moved, and the gap between them has been adjusted to the * requested size. *

    - * The effect on this PyByteArray is that: + * The effect on this PyByteArray is that: * *

          * this.offset = f'
    @@ -2277,7 +2285,8 @@
          * where the regions of length a and b=size-(a+d) have been preserved
          * and the gap between them adjusted to specification. The new offset f' is chosen heuristically
          * by the method to optimise the efficiency of repeated adjustment near either end of the array,
    -     * e.g. repeated prepend or append operations. The effect on this PyByteArray is that:
    +     * e.g. repeated prepend or append operations. The effect on this PyByteArray is
    +     * that:
          *
          * 
          * this.offset = f'
    @@ -2357,7 +2366,8 @@
          * where the regions of length a and b=size-(a+d) have been preserved
          * and the gap between them adjusted to specification. The new offset f' is chosen heuristically
          * by the method to optimise the efficiency of repeated adjustment near either end of the array,
    -     * e.g. repeated prepend or append operations. The effect on this PyByteArray is that:
    +     * e.g. repeated prepend or append operations. The effect on this PyByteArray is
    +     * that:
          *
          * 
          * this.offset = f'
    @@ -2439,7 +2449,7 @@
          *
          * where the contents of region a have been preserved, although possbly moved, and
          * the gap at the end has the requested size. this method never shrinks the total storage. The
    -     * effect on this PyByteArray is that:
    +     * effect on this PyByteArray is that:
          *
          * 
          * this.offset = f or 0
    @@ -2518,7 +2528,7 @@
          * 
    * * where the regions of length a and b=size-(a+d) have been preserved - * and the gap between them eliminated. The effect on this PyByteArray is that: + * and the gap between them eliminated. The effect on this PyByteArray is that: * *
          * this.offset = f'
    @@ -2526,8 +2536,8 @@
          * 
    * * The method does not implement the Python repertoire of slice indices but avoids indexing - * outside the bytearray by silently adjusting a to be within it. Negative d is treated as 0 and - * if d is too large, it is truncated to the array end. + * outside the bytearray by silently adjusting a to be within it. Negative d is + * treated as 0 and if d is too large, it is truncated to the array end. * * @param a index of hole in byte array * @param d number to discard (will discard x[a,a+d-1]) @@ -2536,8 +2546,7 @@ private void storageDelete(int a, int d) { // storageReplace specialised for delete (e=0) - if (d == 0) - { + if (d == 0) { return; // Everything stays where it is. } @@ -2626,7 +2635,7 @@ * where the regions of length a and b=size-(a+e) have been preserved * and the e intervening elements reduced to e-d elements, by removing * exactly the elements with indices (relative to the start of valid data) a+k*c - * for k=0...d-1. The effect on this PyByteArray is that: + * for k=0...d-1. The effect on this PyByteArray is that: * *
          * this.offset = f'
    @@ -2634,8 +2643,8 @@
          * 
    * * The method does not implement the Python repertoire of slice indices but avoids indexing - * outside the bytearray by silently adjusting a to be within it. Negative d is treated as 0 and - * if d is too large, it is truncated to the array end. + * outside the bytearray by silently adjusting a to be within it. Negative d is + * treated as 0 and if d is too large, it is truncated to the array end. * * @param a index of hole in byte array * @param c (>0) step size between the locations of elements to delete @@ -2647,4 +2656,3 @@ // XXX Change SequenceIndexDelegate to use (and PyList to implement) delslice() } } - diff --git a/src/org/python/core/PyDictionary.java b/src/org/python/core/PyDictionary.java --- a/src/org/python/core/PyDictionary.java +++ b/src/org/python/core/PyDictionary.java @@ -32,10 +32,10 @@ public static final PyType TYPE = PyType.fromClass(PyDictionary.class); - private final ConcurrentMap map; + private final ConcurrentMap internalMap; public ConcurrentMap getMap() { - return map; + return internalMap; } /** @@ -50,8 +50,8 @@ */ public PyDictionary(PyType type, int capacity) { super(type); - map = new ConcurrentHashMap(capacity, Generic.CHM_LOAD_FACTOR, - Generic.CHM_CONCURRENCY_LEVEL); + internalMap = new ConcurrentHashMap(capacity, Generic.CHM_LOAD_FACTOR, + Generic.CHM_CONCURRENCY_LEVEL); } /** @@ -59,7 +59,7 @@ */ public PyDictionary(PyType type) { super(type); - map = Generic.concurrentMap(); + internalMap = Generic.concurrentMap(); } /** @@ -75,7 +75,7 @@ public PyDictionary(PyType type, Map map) { this(type, Math.max((int) (map.size() / Generic.CHM_LOAD_FACTOR) + 1, Generic.CHM_INITIAL_CAPACITY)); - this.map.putAll(map); + getMap().putAll(map); } /** @@ -86,9 +86,9 @@ protected PyDictionary(PyType type, boolean initializeBacking) { super(type); if (initializeBacking) { - map = Generic.concurrentMap(); + internalMap = Generic.concurrentMap(); } else { - map = null; // for later initialization + internalMap = null; // for later initialization } } @@ -101,6 +101,7 @@ */ public PyDictionary(PyObject elements[]) { this(); + ConcurrentMap map = getMap(); for (int i = 0; i < elements.length; i += 2) { map.put(elements[i], elements[i + 1]); } @@ -600,7 +601,7 @@ @ExposedMethod(defaults = "null", doc = BuiltinDocs.dict_pop_doc) final PyObject dict_pop(PyObject key, PyObject defaultValue) { - if (!map.containsKey(key)) { + if (!getMap().containsKey(key)) { if (defaultValue == null) { throw Py.KeyError("popitem(): dictionary is empty"); } @@ -738,7 +739,9 @@ return false; } final PyDictionary other = (PyDictionary) obj; - if (this.map != other.map && (this.map == null || !this.map.equals(other.map))) { + ConcurrentMap map = getMap(); + ConcurrentMap otherMap = other.getMap(); + if (map != otherMap && (map == null || !map.equals(otherMap))) { return false; } return true; diff --git a/src/org/python/core/PyString.java b/src/org/python/core/PyString.java --- a/src/org/python/core/PyString.java +++ b/src/org/python/core/PyString.java @@ -2192,10 +2192,7 @@ @ExposedMethod(defaults = {"null", "null"}, doc = BuiltinDocs.str_translate_doc) final String str_translate(String table, String deletechars) { - if (table == null) { - return getString(); - } - if (table.length() != 256) + if (table != null && table.length() != 256) throw Py.ValueError( "translation table must be 256 characters long"); @@ -2204,12 +2201,16 @@ char c = getString().charAt(i); if (deletechars != null && deletechars.indexOf(c) >= 0) continue; - try { - buf.append(table.charAt(c)); - } - catch (IndexOutOfBoundsException e) { - throw Py.TypeError( - "translate() only works for 8-bit character strings"); + if(table == null) { + buf.append(c); + } else { + try { + buf.append(table.charAt(c)); + } + catch (IndexOutOfBoundsException e) { + throw Py.TypeError( + "translate() only works for 8-bit character strings"); + } } } return buf.toString(); diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java --- a/src/org/python/core/PySystemState.java +++ b/src/org/python/core/PySystemState.java @@ -59,6 +59,7 @@ private static final String JAR_URL_PREFIX = "jar:file:"; private static final String JAR_SEPARATOR = "!"; private static final String VFSZIP_PREFIX = "vfszip:"; + private static final String VFS_PREFIX = "vfs:"; public static final PyString version = new PyString(Version.getVersion()); @@ -1183,6 +1184,19 @@ } jarFileName = urlString.substring(start, jarIndex); } + } else if (urlString.startsWith(VFS_PREFIX)) { + // vfs:/some/path/jython.jar/org/python/core/PySystemState.class + final String path = PySystemState.class.getName().replace('.', '/'); + int jarIndex = urlString.indexOf(".jar/".concat(path)); + if (jarIndex > 0) { + jarIndex += 4; + int start = VFS_PREFIX.length(); + if (Platform.IS_WINDOWS) { + // vfs:/C:/some/path/jython.jar/org/python/core/PySystemState.class + start++; + } + jarFileName = urlString.substring(start, jarIndex); + } } } catch (Exception e) {} } diff --git a/src/org/python/core/PyXRange.java b/src/org/python/core/PyXRange.java --- a/src/org/python/core/PyXRange.java +++ b/src/org/python/core/PyXRange.java @@ -127,8 +127,7 @@ @Override protected PyObject getslice(int start, int stop, int step) { - // not supported - return null; + throw Py.TypeError("xrange index must be integer, not 'slice'"); } @Override diff --git a/src/org/python/core/buffer/BaseBuffer.java b/src/org/python/core/buffer/BaseBuffer.java --- a/src/org/python/core/buffer/BaseBuffer.java +++ b/src/org/python/core/buffer/BaseBuffer.java @@ -22,7 +22,7 @@ * passed to the constructor. Otherwise, all methods for write access raise a * BufferError read-only exception and {@link #isReadonly()} returns true. * Sub-classes can follow the same pattern, setting {@link PyBUF#WRITABLE} in the constructor and, - * if they have to override the operations that write (storeAt and + * if they have to, overriding the operations that write (storeAt and * copyFrom). The recommended pattern is: * *
    @@ -31,9 +31,10 @@
      * }
      * // ... implementation of the write operation
      * 
    - * - * The implementors of simple buffers will find it efficient to override the generic access methods - * to which performance might be sensitive, with a calculation specific to their actual type. + * Another approach, used in the standard library, is to have distinct classes for the writable and + * read-only variants. The implementors of simple buffers will find it efficient to override the + * generic access methods to which performance might be sensitive, with a calculation specific to + * their actual type. *

    * At the time of writing, only one-dimensional buffers of item size one are used in the Jython * core. diff --git a/src/org/python/core/buffer/SimpleBuffer.java b/src/org/python/core/buffer/SimpleBuffer.java --- a/src/org/python/core/buffer/SimpleBuffer.java +++ b/src/org/python/core/buffer/SimpleBuffer.java @@ -48,6 +48,7 @@ * @throws ArrayIndexOutOfBoundsException if index0 and size are * inconsistent with storage.length */ + // XXX: "for sub-class use" = should be protected? public SimpleBuffer(byte[] storage, int index0, int size) throws PyException, ArrayIndexOutOfBoundsException { this(); @@ -90,6 +91,7 @@ * @param storage the array of bytes storing the implementation of the exporting object * @throws NullPointerException if storage is null */ + // XXX: "for sub-class use" = should be protected? public SimpleBuffer(byte[] storage) throws NullPointerException { this(); this.storage = storage; // Exported data (index0=0 from initialisation) diff --git a/src/org/python/core/buffer/SimpleStringBuffer.java b/src/org/python/core/buffer/SimpleStringBuffer.java --- a/src/org/python/core/buffer/SimpleStringBuffer.java +++ b/src/org/python/core/buffer/SimpleStringBuffer.java @@ -6,11 +6,11 @@ /** * Buffer API that appears to be a one-dimensional array of one-byte items providing read-only API, * but which is actually backed by a Java String. Some of the buffer API absolutely needs access to - * the data as a byte array (those parts that involve a {@link PyBuffer.Pointer} result), and therefore - * this class must create a byte array from the String for them. However, it defers creation of a - * byte array until that part of the API is actually used. Where possible, this class overrides - * those methods in SimpleBuffer that would otherwise access the byte array attribute to use the - * String instead. + * the data as a byte array (those parts that involve a PyBuffer.Pointer result), and + * therefore this class must create a byte array from the String for them. However, it defers + * creation of a byte array until that part of the API is actually used. Where possible, this class + * overrides those methods in SimpleBuffer that would otherwise access the byte array attribute to + * use the String instead. */ public class SimpleStringBuffer extends SimpleBuffer { diff --git a/src/org/python/core/buffer/Strided1DBuffer.java b/src/org/python/core/buffer/Strided1DBuffer.java --- a/src/org/python/core/buffer/Strided1DBuffer.java +++ b/src/org/python/core/buffer/Strided1DBuffer.java @@ -9,21 +9,24 @@ * properties in the usual way, designating a slice (or all) of a byte array, but also a * stride property (equal to getStrides()[0]). *

    - * Let this underlying buffer be the byte array u(i) for i=0..N-1, let x be the + * Let the underlying buffer be the byte array u(i) for i=0..N-1, let x be the * Strided1DBuffer, and let the stride be p. The storage works as follows. * Designate by x(j), for j=0..L-1, the byte at index j, that is, the byte - * retrieved by x.byteAt(j). Then, we store x(j) at u(a+pj), that is, - * x(0) is at u(a). When we construct such a buffer, we have to supply a = + * retrieved by x.byteAt(j). Thus, we store x(j) at u(a+pj), that is, + * x(0) = u(a). When we construct such a buffer, we have to supply a = * index0, L = length, and p = stride as the * constructor arguments. The last item in the slice x(L-1) is stored at u(a+p(L-1)). - * If p<0 and L>1, this will be to the left of u(a), so the constructor - * argument index0 is not then the low index of the range occupied by the data. Clearly both these - * indexes must be in the range 0 to N-1 inclusive, a rule enforced by the constructors + * For the simple case of positive stride, constructor argument index0 is the low index + * of the range occupied by the data. When the stride is negative, that is to say p<0, and + * L>1, this will be to the left of u(a), and the constructor argument + * index0 is not then the low index of the range occupied by the data. Clearly both + * these indexes must be in the range 0 to N-1 inclusive, a rule enforced by the constructors * (unless L=0, when it is assumed no array access will take place). *

    * The class may be used by exporters to create a strided slice (e.g. to export the diagonal of a * matrix) and in particular by other buffers to create strided slices of themselves, such as to - * create the memoryview that is returned as an extended slice of a memoryview. + * create the memoryview that is returned as an extended slice of a + * memoryview. */ public class Strided1DBuffer extends BaseBuffer { @@ -60,9 +63,14 @@ /** * Provide an instance of Strided1DBuffer with navigation variables initialised, - * for sub-class use. The buffer ( {@link #storage}, {@link #index0}), and the navigation ( + * for sub-class use. The buffer ({@link #storage}, {@link #index0}), and the navigation ( * {@link #shape} array and {@link #stride}) will be initialised from the arguments (which are * checked for range). + *

    + * The sub-class constructor should check that the intended access is compatible with this + * object by calling {@link #checkRequestFlags(int)}. (See the source of + * {@link Strided1DWritableBuffer#Strided1DWritableBuffer(int, byte[], int, int, int)} + * for an example of this use.) * * @param storage raw byte array containing exported data * @param index0 index into storage of item[0] @@ -72,6 +80,7 @@ * @throws ArrayIndexOutOfBoundsException if index0, length and * stride are inconsistent with storage.length */ + // XXX: "for sub-class use" = should be protected? public Strided1DBuffer(byte[] storage, int index0, int length, int stride) throws ArrayIndexOutOfBoundsException, NullPointerException { this(); diff --git a/src/org/python/core/buffer/Strided1DWritableBuffer.java b/src/org/python/core/buffer/Strided1DWritableBuffer.java --- a/src/org/python/core/buffer/Strided1DWritableBuffer.java +++ b/src/org/python/core/buffer/Strided1DWritableBuffer.java @@ -88,7 +88,7 @@ if (length > 0) { // Translate start relative to underlying buffer - int compStride= this.stride * stride; + int compStride = this.stride * stride; int compIndex0 = index0 + start * this.stride; // Construct a view, taking a lock on the root object (this or this.root) return new SlicedView(getRoot(), flags, storage, compIndex0, length, compStride); diff --git a/src/org/python/core/codecs.java b/src/org/python/core/codecs.java --- a/src/org/python/core/codecs.java +++ b/src/org/python/core/codecs.java @@ -919,11 +919,11 @@ * This method differs from the CPython equivalent (in Object/unicodeobject.c) * which works with an array of code points that are, in a wide build, Unicode code points. * - * @param unicode - * @param base64SetO - * @param base64WhiteSpace - * @param errors - * @return + * @param unicode to be encoded + * @param base64SetO true if characters in "set O" should be translated to base64 + * @param base64WhiteSpace true if white-space characters should be translated to base64 + * @param errors error policy name (e.g. "ignore", "replace") + * @return bytes representing the encoded unicode string */ public static String PyUnicode_EncodeUTF7(String unicode, boolean base64SetO, boolean base64WhiteSpace, String errors) { diff --git a/src/org/python/core/io/StreamIO.java b/src/org/python/core/io/StreamIO.java --- a/src/org/python/core/io/StreamIO.java +++ b/src/org/python/core/io/StreamIO.java @@ -276,63 +276,7 @@ } private static ReadableByteChannel newChannel(InputStream in) { - return new InternalReadableByteChannel(in); - } - - - /* - * AbstractInterruptibleChannel is used for its end() and begin() implementations - * but this Channel is not really interruptible. - */ - private static class InternalReadableByteChannel - extends AbstractInterruptibleChannel - implements ReadableByteChannel { - - private InputStream in; - private boolean open = true; - - InternalReadableByteChannel(InputStream in) { - this.in = in; - } - - public int read(ByteBuffer dst) throws IOException { - final int CHUNK = 8192; - - int len = dst.remaining(); - int totalRead = 0; - int bytesRead = 0; - - byte buf[] = new byte[0]; - while (totalRead < len) { - int bytesToRead = Math.min((len - totalRead), CHUNK); - if (buf.length < bytesToRead) { - buf = new byte[bytesToRead]; - } - try { - begin(); - bytesRead = in.read(buf, 0, bytesToRead); - } finally { - end(bytesRead > 0); - } - if (bytesRead < 0) { - break; - } else { - totalRead += bytesRead; - } - dst.put(buf, 0, bytesRead); - } - if ((bytesRead < 0) && (totalRead == 0)) { - return -1; - } - return totalRead; - } - - protected void implCloseChannel() throws IOException { - if (open) { - in.close(); - open = false; - } - } + return Channels.newChannel(in); } } diff --git a/src/org/python/modules/SHA224Digest.java b/src/org/python/modules/SHA224Digest.java --- a/src/org/python/modules/SHA224Digest.java +++ b/src/org/python/modules/SHA224Digest.java @@ -1,6 +1,6 @@ package org.python.modules; -/** +/* * Copyright 2011 Gaurav Raje * Licensed to PSF under a Contributor Agreement. */ @@ -8,13 +8,14 @@ /** * SHA-224 as described in RFC 3874. This introduces the SHA224 Digest which has - * been ommitted from the JDK {@link java.security}. - * + * been omitted from the JDK java.security. * * This implementation has been borrowed from the Bouncy Castle implementation - * of SHA2 algorithms. Since they are MIT Licensed, they are compatible with + * of SHA2 algorithms. + * + * Since they are MIT Licensed, they are compatible with * this project. Their mandatory copyright notice follows. - * + *

      * Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle
      * (http://www.bouncycastle.org)
      * 
    @@ -35,6 +36,7 @@
      * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      * SOFTWARE.
    + * 
    */ public class SHA224Digest extends MessageDigest diff --git a/src/org/python/modules/_codecs.java b/src/org/python/modules/_codecs.java --- a/src/org/python/modules/_codecs.java +++ b/src/org/python/modules/_codecs.java @@ -314,12 +314,12 @@ * @param mapping to convert bytes to characters * @return decoded string and number of bytes consumed */ - public static PyTuple charmap_decode(String str, String errors, PyObject mapping) { + public static PyTuple charmap_decode(String bytes, String errors, PyObject mapping) { if (mapping == null || mapping == Py.None) { // Default to Latin-1 - return latin_1_decode(str, errors); + return latin_1_decode(bytes, errors); } else { - return charmap_decode(str, errors, mapping, false); + return charmap_decode(bytes, errors, mapping, false); } } @@ -1290,7 +1290,6 @@ * @param bytes to be decoded (Jython {@link PyString} convention) * @param errors error policy name (e.g. "ignore", "replace") * @param byteorder decoding "endianness" specified (in the Python -1, 0, +1 convention) - * @param isFinal if a "final" call, meaning the input must all be consumed * @return tuple (unicode_result, bytes_consumed, endianness) */ public static PyTuple utf_32_ex_decode(String bytes, String errors, int byteorder) { @@ -1335,7 +1334,6 @@ * @param order LE, BE or UNDEFINED (meaning bytes may begin with a byte order mark) * @param isFinal if a "final" call, meaning the input must all be consumed * @param findOrder if the returned tuple should include a report of the byte order - * @return tuple containing unicode result (as UTF-16 Java String) * @return tuple (unicode_result, bytes_consumed [, endianness]) */ private static PyTuple PyUnicode_DecodeUTF32Stateful(String bytes, String errors, diff --git a/src/org/python/modules/_csv/PyReader.java b/src/org/python/modules/_csv/PyReader.java --- a/src/org/python/modules/_csv/PyReader.java +++ b/src/org/python/modules/_csv/PyReader.java @@ -69,11 +69,15 @@ lineobj = input_iter.__iternext__(); if (lineobj == null) { // End of input OR exception - if (field.length() != 0) { - throw _csv.Error("newline inside string"); - } else { - return null; + if (field.length() != 0 || state == ParserState.IN_QUOTED_FIELD) { + if (dialect.strict) { + throw _csv.Error("unexpected end of data"); + } else { + parse_save_field(); + break; + } } + return null; } line_num++; diff --git a/src/org/python/modules/_csv/PyWriter.java b/src/org/python/modules/_csv/PyWriter.java --- a/src/org/python/modules/_csv/PyWriter.java +++ b/src/org/python/modules/_csv/PyWriter.java @@ -3,6 +3,7 @@ import org.python.core.Py; import org.python.core.PyException; +import org.python.core.PyFloat; import org.python.core.PyObject; import org.python.core.PyString; import org.python.core.PyType; @@ -130,7 +131,16 @@ } else if (field == Py.None) { append_ok = join_append("", len == 1); } else { - PyObject str = field.__str__(); + + PyObject str; + //XXX: in 3.x this check can go away and we can just always use + // __str__ + if (field.getClass() == PyFloat.class) { + str = field.__repr__(); + } else { + str = field.__str__(); + } + if (str == null) { return false; } diff --git a/src/org/python/modules/_io/OpenMode.java b/src/org/python/modules/_io/OpenMode.java --- a/src/org/python/modules/_io/OpenMode.java +++ b/src/org/python/modules/_io/OpenMode.java @@ -43,8 +43,7 @@ /** * Error message describing the way in which the mode is invalid, or null if no problem has been * found. This field may be set by the constructor (in the case of duplicate or unrecognised - * mode letters), by the {@link #isValid()} method, or by client code. A non-null value will - * cause {@link #isValid()} to return false. + * mode letters), by the {@link #validate()} method, or by client code. */ public String message; @@ -52,7 +51,7 @@ * Decode the given string to an OpenMode object, checking for duplicate or unrecognised mode * letters. Valid letters are those in "rwa+btU". Errors in the mode string do not raise an * exception, they simply generate an appropriate error message in {@link #message}. After - * construction, a client should always call {@link #isValid()} to complete validity checks. + * construction, a client should always call {@link #validate()} to complete validity checks. * * @param mode */ @@ -196,7 +195,7 @@ */ public void checkValid() throws PyException { - // Actually peform the check + // Actually perform the check validate(); // The 'other' flag reports alien symbols in the original mode string @@ -215,7 +214,7 @@ /** * The mode string we need when constructing a FileIO initialised with the present * mode. Note that this is not the same as the full open mode because it omits the text-based - * attributes, and not the same as {@link #raw()}. + * attributes. * * @return "r", "w", or "a" with optional "+". */ diff --git a/src/org/python/modules/_io/PyFileIO.java b/src/org/python/modules/_io/PyFileIO.java --- a/src/org/python/modules/_io/PyFileIO.java +++ b/src/org/python/modules/_io/PyFileIO.java @@ -1,15 +1,10 @@ -/* Copyright (c)2012 Jython Developers */ +/* Copyright (c)2013 Jython Developers */ package org.python.modules._io; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.nio.channels.Channel; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.WritableByteChannel; import org.python.core.ArgParser; import org.python.core.BuiltinDocs; @@ -26,6 +21,7 @@ import org.python.core.PyUnicode; import org.python.core.io.FileIO; import org.python.core.io.RawIOBase; +import org.python.core.io.StreamIO; import org.python.expose.ExposedGet; import org.python.expose.ExposedMethod; import org.python.expose.ExposedNew; @@ -39,8 +35,8 @@ public static final PyType TYPE = PyType.fromClass(PyFileIO.class); - /** The FileIO to which we delegate operations not complete locally. */ - private FileIO ioDelegate; + /** The {@link FileIO} or {@link StreamIO} to which we delegate operations not complete locally. */ + private RawIOBase ioDelegate; /* * Implementation note: CPython fileio does not use the base-class, possibly overridden, @@ -105,12 +101,15 @@ */ public PyFileIO(PyType subtype, PyObject file, OpenMode mode, boolean closefd) { super(subtype); - setDelegate(file, mode, closefd); - this.closefd = closefd; + // Establish the direction(s) of flow readable = mode.reading | mode.updating; writable = mode.writing | mode.updating | mode.appending; + // Assign a delegate according to the file argument + this.closefd = closefd; + setDelegate(file, mode); + // The mode string of a raw file always asserts it is binary: "rb", "rb+", or "wb". if (readable) { this.mode = new PyString(writable ? "rb+" : "rb"); @@ -132,9 +131,8 @@ * * @param file name or descriptor * @param mode parsed file open mode - * @param closefd must be true if file is in fact a name (checked, not used) */ - private void setDelegate(PyObject file, OpenMode mode, boolean closefd) { + private void setDelegate(PyObject file, OpenMode mode) { if (file instanceof PyString) { // Open a file by name @@ -151,23 +149,13 @@ */ Object fd = file.__tojava__(Object.class); - if (fd instanceof RawIOBase) { - // It is the "Jython file descriptor" from which we can get a channel. + if (fd instanceof FileIO || fd instanceof StreamIO) { /* - * If the argument were a FileIO, could we assign it directly to ioDelegate? I think - * not: there would be a problem with close and closefd=False since it is not the - * PyFileIO that keeps state. + * It is the "Jython file descriptor", of a type suitable to be the ioDelegate. The + * allowed types are able to give us a non-null InputStream or OutputStream, + * according to direction. */ - Channel channel = ((RawIOBase)fd).getChannel(); - if (channel instanceof FileChannel) { - if (channel.isOpen()) { - FileChannel fc = (FileChannel)channel; - ioDelegate = new FileIO(fc, mode.forFileIO()); - } else { - // File not open (we have to check as FileIO doesn't) - throw Py.OSError(Errno.EBADF); - } - } + ioDelegate = (RawIOBase)fd; } } @@ -175,11 +163,22 @@ if (ioDelegate == null) { // The file was a type we don't know how to use throw Py.TypeError(String.format("invalid file: %s", file.__repr__().asString())); + + } else { + + if (ioDelegate.closed()) { + // A closed file descriptor is a "bad descriptor" + throw Py.OSError(Errno.EBADF); + } + + if ((readable && !ioDelegate.readable()) || (writable && !ioDelegate.writable())) { + // Requested mode in conflict with underlying file or stream + throw tailoredValueError(readable ? "read" : "writ"); + } + + // The name is either the textual name or a file descriptor (see Python docs) + fastGetDict().__setitem__("name", file); } - - // The name is either the textual name or a file descriptor (see Python docs) - fastGetDict().__setitem__("name", file); - } private static final String[] openArgs = {"file", "mode", "closefd"}; @@ -239,8 +238,8 @@ PyArray a = (PyArray)buf; try { - ReadableByteChannel ch = ioDelegate.getChannel(); - InputStream is = Channels.newInputStream(ch); + // The ioDelegate, if readable, can always provide an InputStream (see setDelegate) + InputStream is = ioDelegate.asInputStream(); count = a.fillFromStream(is); count *= a.getItemsize(); } catch (IOException ioe) { @@ -281,8 +280,8 @@ if (buf instanceof PyArray) { // Special case: PyArray knows how to write itself try { - WritableByteChannel ch = ioDelegate.getChannel(); - OutputStream os = Channels.newOutputStream(ch); + // The ioDelegate, if writable, can always provide an OutputStream (see setDelegate) + OutputStream os = ioDelegate.asOutputStream(); count = ((PyArray)buf).toStream(os); } catch (IOException ioe) { throw Py.IOError(ioe); diff --git a/src/org/python/modules/_io/PyIOBase.java b/src/org/python/modules/_io/PyIOBase.java --- a/src/org/python/modules/_io/PyIOBase.java +++ b/src/org/python/modules/_io/PyIOBase.java @@ -160,7 +160,6 @@ * Truncate file to size bytes to the current position (as reported by * tell()). * - * @param size requested for stream (or null for default) * @return the new size */ public long truncate() { @@ -182,7 +181,10 @@ } @ExposedMethod(doc = flush_doc) - final void _IOBase_flush() {} + final void _IOBase_flush() { + // Even types for which this remains a no-op must complain if closed (e.g. BytesIO) + _checkClosed(); + } /** * True if the object is closed to further client operations. It is the state accessed by @@ -592,7 +594,7 @@ */ PyByteArray res = new PyByteArray(); - while (remainingLimit > 0) { + while (--remainingLimit >= 0) { /* * read() returns a str of one byte, doing at most one read to refill, or it returns diff --git a/src/org/python/modules/_io/PyRawIOBase.java b/src/org/python/modules/_io/PyRawIOBase.java --- a/src/org/python/modules/_io/PyRawIOBase.java +++ b/src/org/python/modules/_io/PyRawIOBase.java @@ -187,7 +187,7 @@ * of bytes written. * * @param b buffer of bytes to be written - * @return + * @return the number of bytes written */ public PyObject write(PyObject b) { return _RawIOBase_write(b); diff --git a/src/org/python/modules/_sre.java b/src/org/python/modules/_sre.java --- a/src/org/python/modules/_sre.java +++ b/src/org/python/modules/_sre.java @@ -22,6 +22,9 @@ public class _sre { public static int MAGIC = SRE_STATE.SRE_MAGIC; + // probably the right number for Jython since we are UTF-16. + public static int MAXREPEAT = 65535; + // workaround the fact that H, I types are unsigned, but we are not really using them as such // XXX: May not be the right size, but I suspect it is -- see sre_compile.py public static int CODESIZE = 4; diff --git a/src/org/python/modules/_threading/Condition.java b/src/org/python/modules/_threading/Condition.java --- a/src/org/python/modules/_threading/Condition.java +++ b/src/org/python/modules/_threading/Condition.java @@ -38,12 +38,16 @@ } public boolean acquire() { - return Condition_acquire(); + return Condition_acquire(true); } - @ExposedMethod - final boolean Condition_acquire() { - return _lock.acquire(); + public boolean acquire(boolean blocking) { + return Condition_acquire(blocking); + } + + @ExposedMethod(defaults = "true") + final boolean Condition_acquire(boolean blocking) { + return _lock.acquire(blocking); } public PyObject __enter__(ThreadState ts) { @@ -53,7 +57,7 @@ @ExposedMethod final PyObject Condition___enter__() { - Condition_acquire(); + Condition_acquire(true); return this; } diff --git a/src/org/python/modules/time/Time.java b/src/org/python/modules/time/Time.java --- a/src/org/python/modules/time/Time.java +++ b/src/org/python/modules/time/Time.java @@ -780,6 +780,11 @@ builder.append("'"); inQuote = needsQuote; } + if (charAt == '\'') { + // a single quote always needs to be escaped, regardless + // whether already in a quote or not + builder.append("'"); + } builder.append(charAt); continue; } else if (inQuote) { diff --git a/src/org/python/util/ProxyCompiler.java b/src/org/python/util/ProxyCompiler.java --- a/src/org/python/util/ProxyCompiler.java +++ b/src/org/python/util/ProxyCompiler.java @@ -5,21 +5,22 @@ import org.python.core.PySystemState; public class ProxyCompiler { + /** * Compiles the python file by loading it - * - * FIXME: this is quite hackish right now. It basically starts a whole interpreter - * and set's proxyDebugDirectory as the destination for the compiled proxy class - * - * @param filename: python filename to exec - * @param destDir: destination directory for the proxy classes + * + * FIXME: this is quite hackish right now. It basically starts a whole interpreter and sets + * proxyDebugDirectory as the destination for the compiled proxy class + * + * @param filename python filename to exec + * @param destDir destination directory for the proxy classes */ public static void compile(String filename, String destDir) { Properties props = new Properties(System.getProperties()); props.setProperty(PySystemState.PYTHON_CACHEDIR_SKIP, "true"); PySystemState.initialize(props, null); PythonInterpreter interp = new PythonInterpreter(); - + String origProxyDir = org.python.core.Options.proxyDebugDirectory; try { org.python.core.Options.proxyDebugDirectory = destDir; diff --git a/tests/java/org/python/modules/_io/_ioTest.java b/tests/java/org/python/modules/_io/_ioTest.java --- a/tests/java/org/python/modules/_io/_ioTest.java +++ b/tests/java/org/python/modules/_io/_ioTest.java @@ -5,9 +5,15 @@ import static org.junit.matchers.JUnitMatchers.*; import java.io.File; +import java.io.FileDescriptor; import java.io.FileInputStream; -import java.io.IOError; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; import java.util.Arrays; import org.junit.Before; @@ -18,6 +24,7 @@ import org.python.core.PyObject; import org.python.core.PyStringMap; import org.python.core.PySystemState; +import org.python.core.imp; import org.python.core.io.RawIOBase; import org.python.util.PythonInterpreter; @@ -28,7 +35,12 @@ */ public class _ioTest { - /** We need the interpreter to be initialised for these tests **/ + // Some file names to use + private final String FILE1 = "$test_1_tmp"; + private final String FILE2 = "$test_2_tmp"; + private final String FILE3 = "$test_3_tmp"; + + // We need the interpreter to be initialised for these tests. PySystemState systemState; PyStringMap dict; PythonInterpreter interp; @@ -94,6 +106,61 @@ } } + /** Check PyFile().fileno() is acceptable to _io.open() */ + @Test + public void openPyFileByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + PyFile file = new PyFile(FILE1, "w", 1); + openByFilenoTest(file, "wb"); + } + + /** Check PyFile(OutputStream).fileno() is acceptable to _io.open() */ + @Test + public void openPyFileOStreamByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + OutputStream ostream = new FileOutputStream(FILE1); + PyFile file = new PyFile(ostream); + openByFilenoTest(file, "wb"); + } + + /** Check sys.stdin.fileno() is acceptable to _io.open() */ + @Test + public void openStdinByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + openByFilenoTest(sys.stdin, "rb"); + } + + /** Check sys.stdout.fileno() is acceptable to _io.open() */ + @Test + public void openStdoutByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + openByFilenoTest(sys.stdout, "wb"); + } + + /** Check sys.stderr.fileno() is acceptable to _io.open() */ + @Test + public void openStderrByFileno() throws IOException { + PySystemState sys = Py.getSystemState(); + openByFilenoTest(sys.stderr, "wb"); + } + + /** + * Test opening by a "file descriptor" obtained from another file or stream. The main purpose is + * to test that the return from fileno() is acceptable to _io.open(). + * + * @param file anything with a "fileno" function + * @param mode mode string "rb" etc. + * @throws IOException + */ + public void openByFilenoTest(PyObject file, String mode) throws IOException { + PyObject pyfd = file.invoke("fileno"); + RawIOBase fd = (RawIOBase)pyfd.__tojava__(RawIOBase.class); + PyObject[] args = new PyObject[] {pyfd, Py.newString(mode), Py.False}; + String[] kwds = {"closefd"}; + PyObject file2 = _io.open(args, kwds); + file2.invoke("close"); + } + /** * Test automatic closing of files when the interpreter finally exits. Done correctly, text * written to any kind of file-like object should be flushed to disk and the file closed when @@ -106,9 +173,9 @@ public void closeNeglectedFiles() throws IOException { // File names - final String F = "$test_1_tmp"; // Raw file - final String FB = "$test_2_tmp"; // Buffered file - final String FT = "$test_3_tmp"; // Test file + final String F = FILE1; // Raw file + final String FB = FILE2; // Buffered file + final String FT = FILE3; // Test file String expText = "Line 1\nLine 2\nLine 3."; // Note: all ascii, but with new lines byte[] expBytes = expText.getBytes(); @@ -149,11 +216,11 @@ assertTrue(pyft.__closed); // If they were not closed properly not all bytes will reach the files. - checkFileContent(F, expBytes); - checkFileContent(FB, expBytes); + checkFileContent(F, expBytes, true); + checkFileContent(FB, expBytes, true); // Expect that TextIOWrapper should have adjusted the line separator - checkFileContent(FT, newlineFix(expText)); + checkFileContent(FT, newlineFix(expText), true); } /** @@ -165,9 +232,10 @@ @Test public void closeNeglectedPyFiles() throws IOException { - final String F = "$test_1_tmp"; // Raw file - final String FB = "$test_2_tmp"; // Buffered file - final String FT = "$test_3_tmp"; // Test file + // File names + final String F = FILE1; // Raw file + final String FB = FILE2; // Buffered file + final String FT = FILE3; // Test file String expText = "Line 1\nLine 2\nLine 3."; byte[] expBytes = expText.getBytes(); @@ -183,19 +251,19 @@ interp.exec("f = open('" + F + "', 'wb', 0)"); PyFile pyf = (PyFile)interp.get("f"); assertNotNull(pyf); - RawIOBase r = (RawIOBase) pyf.fileno().__tojava__(RawIOBase.class); + RawIOBase r = (RawIOBase)pyf.fileno().__tojava__(RawIOBase.class); // This should get us a buffered binary PyFile called fb interp.exec("fb = open('" + FB + "', 'wb')"); PyFile pyfb = (PyFile)interp.get("fb"); assertNotNull(pyfb); - RawIOBase rb = (RawIOBase) pyfb.fileno().__tojava__(RawIOBase.class); + RawIOBase rb = (RawIOBase)pyfb.fileno().__tojava__(RawIOBase.class); // This should get us an buffered text PyFile called ft interp.exec("ft = open('" + FT + "', 'w')"); PyFile pyft = (PyFile)interp.get("ft"); assertNotNull(pyft); - RawIOBase rt = (RawIOBase) pyft.fileno().__tojava__(RawIOBase.class); + RawIOBase rt = (RawIOBase)pyft.fileno().__tojava__(RawIOBase.class); // Write the bytes test material to each file but don't close it interp.exec("f.write(b)"); @@ -211,39 +279,47 @@ assertTrue(rt.closed()); // If they were not closed properly not all bytes will reach the files. - checkFileContent(F, expBytes); - checkFileContent(FB, expBytes); + checkFileContent(F, expBytes, true); + checkFileContent(FB, expBytes, true); // Expect that TextIOWrapper should have adjusted the line separator - checkFileContent(FT, newlineFix(expText)); + checkFileContent(FT, newlineFix(expText), true); } /** - * Check the file contains the bytes we expect and delete the file. If it was not closed - * properly (layers in the right order and a flush) not all bytes will have reached the files. + * Check the file contains the bytes we expect and optionally delete the file. If it was + * not closed properly (layers in the right order and a flush) not all bytes will have reached + * the files. * * @param name of file * @param expBytes expected + * @param delete the file if true * @throws IOException if cannot open/read */ - private static void checkFileContent(String name, byte[] expBytes) throws IOException { + private static void checkFileContent(String name, byte[] expBytes, boolean delete) + throws IOException { + // Open and read byte[] r = new byte[2 * expBytes.length]; File f = new File(name); FileInputStream in = new FileInputStream(f); int n = in.read(r); in.close(); + // Check as expected String msg = "Bytes read from " + name; assertEquals(msg, expBytes.length, n); byte[] resBytes = Arrays.copyOf(r, n); assertArrayEquals(msg, expBytes, resBytes); - f.delete(); + // Delete the file + if (delete) { + f.delete(); + } } - /** * Replace "\n" characters by the system-defined newline sequence and return as bytes. + * * @param expText to translate * @return result as bytes */ -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Mar 19 21:24:42 2013 From: jython-checkins at python.org (jim.baker) Date: Tue, 19 Mar 2013 21:24:42 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Fixed_copyright_date?= Message-ID: <3ZVm2f1gYGzS2q@mail.python.org> http://hg.python.org/jython/rev/2bf9afede3ec changeset: 7088:2bf9afede3ec user: Jim Baker date: Tue Mar 19 13:23:32 2013 -0700 summary: Fixed copyright date files: src/org/python/core/PySystemState.java | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java --- a/src/org/python/core/PySystemState.java +++ b/src/org/python/core/PySystemState.java @@ -93,7 +93,7 @@ */ public static final PyObject copyright = Py.newString( - "Copyright (c) 2000-2009 Jython Developers.\n" + + "Copyright (c) 2000-2013 Jython Developers.\n" + "All rights reserved.\n\n" + "Copyright (c) 2000 BeOpen.com.\n" + -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Mar 19 22:08:19 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Tue, 19 Mar 2013 22:08:19 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Unskip_on_some_improvements?= =?utf-8?q?=2E?= Message-ID: <3ZVn0z56JszS8W@mail.python.org> http://hg.python.org/jython/rev/56b8e3f2b82e changeset: 7089:56b8e3f2b82e user: Frank Wierzbicki date: Tue Mar 19 14:07:38 2013 -0700 summary: Unskip on some improvements. files: Lib/test/test_java_integration.py | 5 +---- 1 files changed, 1 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -109,7 +109,6 @@ def tearDown(self): sys.stdout = self.orig_stdout - @unittest.skip("FIXME: check after ASM 4.0 update is complete.") def test_stdout_outputstream(self): out = FileOutputStream(test_support.TESTFN) sys.stdout = out @@ -121,11 +120,9 @@ class IOTest(unittest.TestCase): - @unittest.skip("FIXME: check after ASM 4.0 update is complete.") def test_io_errors(self): "Check that IOException isn't mangled into an IOError" self.assertRaises(UnsupportedEncodingException, OutputStreamWriter, System.out, "garbage") - self.assertRaises(IOError, OutputStreamWriter, System.out, "garbage") def test_fileio_error(self): self.assertRaises(FileNotFoundException, FileInputStream, "garbage") @@ -428,7 +425,7 @@ class SecurityManagerTest(unittest.TestCase): - @unittest.skip("FIXME: check after ASM 4.0 update is complete.") + @unittest.skip("XXX: not working") def test_nonexistent_import_with_security(self): if os._name == 'nt': # http://bugs.jython.org/issue1371 -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Mar 19 22:10:46 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Tue, 19 Mar 2013 22:10:46 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Unskip_fixed=2E?= Message-ID: <3ZVn3p28jHzRCY@mail.python.org> http://hg.python.org/jython/rev/129a1c29c280 changeset: 7090:129a1c29c280 user: Frank Wierzbicki date: Tue Mar 19 14:10:28 2013 -0700 summary: Unskip fixed. files: Lib/test/test_os_jy.py | 2 -- 1 files changed, 0 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_os_jy.py b/Lib/test/test_os_jy.py --- a/Lib/test/test_os_jy.py +++ b/Lib/test/test_os_jy.py @@ -18,12 +18,10 @@ def test_issue1727(self): os.stat(*(test_support.TESTFN,)) - @unittest.skip("FIXME: broken") def test_issue1755(self): os.remove(test_support.TESTFN) self.assertRaises(OSError, os.utime, test_support.TESTFN, None) - @unittest.skip("FIXME: broken") def test_issue1824(self): os.remove(test_support.TESTFN) self.assertRaises(OSError, os.link, -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Tue Mar 19 22:16:28 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Tue, 19 Mar 2013 22:16:28 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Removing_test_that_doesn=27?= =?utf-8?q?t_work_on_CPython_2=2E7=2E?= Message-ID: <3ZVnBN41Q1zSMv@mail.python.org> http://hg.python.org/jython/rev/2445b0e2b4c9 changeset: 7091:2445b0e2b4c9 user: Frank Wierzbicki date: Tue Mar 19 14:16:04 2013 -0700 summary: Removing test that doesn't work on CPython 2.7. files: Lib/test/test_seq_jy.py | 2 -- 1 files changed, 0 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_seq_jy.py b/Lib/test/test_seq_jy.py --- a/Lib/test/test_seq_jy.py +++ b/Lib/test/test_seq_jy.py @@ -35,7 +35,6 @@ for type2test in self.types2test: self.assertTrue(type2test() in foo) - @unittest.skip("FIXME: broken") def test_seq_subclass_equality(self): # Various combinations of PyObject._eq, overriden Object.equals, # and cmp implementations @@ -45,7 +44,6 @@ return False l = type2test(['bar', 'baz']) foo = Foo(l) - self.assertNotEqual(l, foo) self.assertEqual(cmp(l, foo), 1) self.assertEqual(cmp(foo, foo), 0) -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Mar 21 22:18:08 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 21 Mar 2013 22:18:08 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Deliberately_skip_what_look?= =?utf-8?q?s_to_me_like_a_CPython_specific_test=2E?= Message-ID: <3ZX17N2hgFzSJs@mail.python.org> http://hg.python.org/jython/rev/dc35172305e6 changeset: 7092:dc35172305e6 user: Frank Wierzbicki date: Thu Mar 21 14:17:49 2013 -0700 summary: Deliberately skip what looks to me like a CPython specific test. files: Lib/test/test_StringIO.py | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_StringIO.py b/Lib/test/test_StringIO.py --- a/Lib/test/test_StringIO.py +++ b/Lib/test/test_StringIO.py @@ -154,7 +154,8 @@ self.assertEqual(s, 'abcde') self.assertEqual(type(s), str) - if not test_support.is_jython: #FIXME #1862: not working in Jython + # This cStringIO/StringIO difference seems CPython specific to me... + if not test_support.is_jython: self.assertRaises(UnicodeEncodeError, self.MODULE.StringIO, u'\xf4') -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Mar 21 22:28:47 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 21 Mar 2013 22:28:47 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_pydoc=2Epy?= Message-ID: <3ZX1Mg3mFtzSFs@mail.python.org> http://hg.python.org/jython/rev/2966c35e6d65 changeset: 7093:2966c35e6d65 user: Frank Wierzbicki date: Thu Mar 21 14:28:39 2013 -0700 summary: Update pydoc.py files: Lib/pydoc.py | 101 +++++++++++++++++++++++--------------- 1 files changed, 60 insertions(+), 41 deletions(-) diff --git a/Lib/pydoc.py b/Lib/pydoc.py --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -52,7 +52,7 @@ # the current directory is changed with os.chdir(), an incorrect # path will be displayed. -import sys, imp, os, re, types, inspect, __builtin__, pkgutil +import sys, imp, os, re, types, inspect, __builtin__, pkgutil, warnings from repr import Repr from string import expandtabs, find, join, lower, split, strip, rfind, rstrip from traceback import extract_tb @@ -156,7 +156,7 @@ no.append(x) return yes, no -def visiblename(name, all=None): +def visiblename(name, all=None, obj=None): """Decide whether to show documentation on a variable.""" # Certain special names are redundant. _hidden_names = ('__builtins__', '__doc__', '__file__', '__path__', @@ -164,6 +164,9 @@ if name in _hidden_names: return 0 # Private names are hidden, but special names are displayed. if name.startswith('__') and name.endswith('__'): return 1 + # Namedtuples have public fields and methods with a single leading underscore + if name.startswith('_') and hasattr(obj, '_fields'): + return 1 if all is not None: # only document that which the programmer exported in __all__ return name in all @@ -209,8 +212,8 @@ def synopsis(filename, cache={}): """Get the one-line summary out of a module file.""" mtime = os.stat(filename).st_mtime - lastupdate, result = cache.get(filename, (0, None)) - if lastupdate < mtime: + lastupdate, result = cache.get(filename, (None, None)) + if lastupdate is None or lastupdate < mtime: info = inspect.getmoduleinfo(filename) try: file = open(filename) @@ -475,9 +478,9 @@ def multicolumn(self, list, format, cols=4): """Format a list of items into a multi-column list.""" result = '' - rows = (len(list)+cols-1)/cols + rows = (len(list)+cols-1)//cols for col in range(cols): - result = result + '' % (100/cols) + result = result + '' % (100//cols) for i in range(rows*col, rows*col+rows): if i < len(list): result = result + format(list[i]) + '
    \n' @@ -627,7 +630,7 @@ # if __all__ exists, believe it. Otherwise use old heuristic. if (all is not None or (inspect.getmodule(value) or object) is object): - if visiblename(key, all): + if visiblename(key, all, object): classes.append((key, value)) cdict[key] = cdict[value] = '#' + key for key, value in classes: @@ -643,13 +646,13 @@ # if __all__ exists, believe it. Otherwise use old heuristic. if (all is not None or inspect.isbuiltin(value) or inspect.getmodule(value) is object): - if visiblename(key, all): + if visiblename(key, all, object): funcs.append((key, value)) fdict[key] = '#-' + key if inspect.isfunction(value): fdict[value] = fdict[key] data = [] for key, value in inspect.getmembers(object, isdata): - if visiblename(key, all): + if visiblename(key, all, object): data.append((key, value)) doc = self.markup(getdoc(object), self.preformat, fdict, cdict) @@ -737,8 +740,15 @@ hr.maybe() push(msg) for name, kind, homecls, value in ok: - push(self.document(getattr(object, name), name, mod, - funcs, classes, mdict, object)) + try: + value = getattr(object, name) + except Exception: + # Some descriptors may meet a failure in their __get__. + # (bug #1785) + push(self._docdescriptor(name, value, mod)) + else: + push(self.document(value, name, mod, + funcs, classes, mdict, object)) push('\n') return attrs @@ -773,12 +783,17 @@ push('\n') return attrs - attrs = filter(lambda data: visiblename(data[0]), + attrs = filter(lambda data: visiblename(data[0], obj=object), classify_class_attrs(object)) mdict = {} for key, kind, homecls, value in attrs: mdict[key] = anchor = '#' + name + '-' + key - value = getattr(object, key) + try: + value = getattr(object, name) + except Exception: + # Some descriptors may meet a failure in their __get__. + # (bug #1785) + pass try: # The value may not be hashable (e.g., a data attr with # a dict or list value). @@ -1042,18 +1057,18 @@ # if __all__ exists, believe it. Otherwise use old heuristic. if (all is not None or (inspect.getmodule(value) or object) is object): - if visiblename(key, all): + if visiblename(key, all, object): classes.append((key, value)) funcs = [] for key, value in inspect.getmembers(object, inspect.isroutine): # if __all__ exists, believe it. Otherwise use old heuristic. if (all is not None or inspect.isbuiltin(value) or inspect.getmodule(value) is object): - if visiblename(key, all): + if visiblename(key, all, object): funcs.append((key, value)) data = [] for key, value in inspect.getmembers(object, isdata): - if visiblename(key, all): + if visiblename(key, all, object): data.append((key, value)) modpkgs = [] @@ -1158,8 +1173,15 @@ hr.maybe() push(msg) for name, kind, homecls, value in ok: - push(self.document(getattr(object, name), - name, mod, object)) + try: + value = getattr(object, name) + except Exception: + # Some descriptors may meet a failure in their __get__. + # (bug #1785) + push(self._docdescriptor(name, value, mod)) + else: + push(self.document(value, + name, mod, object)) return attrs def spilldescriptors(msg, attrs, predicate): @@ -1186,7 +1208,7 @@ name, mod, maxlen=70, doc=doc) + '\n') return attrs - attrs = filter(lambda data: visiblename(data[0]), + attrs = filter(lambda data: visiblename(data[0], obj=object), classify_class_attrs(object)) while attrs: if mro: @@ -1316,8 +1338,6 @@ def getpager(): """Decide what method to use for paging through text.""" - if sys.platform.startswith('java'): - return plainpager if type(sys.stdout) is not types.FileType: return plainpager if not sys.stdin.isatty() or not sys.stdout.isatty(): @@ -1453,13 +1473,14 @@ else: break if module: object = module - for part in parts[n:]: - try: object = getattr(object, part) - except AttributeError: return None - return object else: - if hasattr(__builtin__, path): - return getattr(__builtin__, path) + object = __builtin__ + for part in parts[n:]: + try: + object = getattr(object, part) + except AttributeError: + return None + return object # --------------------------------------- interactive interpreter interface @@ -1477,7 +1498,8 @@ raise ImportError, 'no Python documentation found for %r' % thing return object, thing else: - return thing, getattr(thing, '__name__', None) + name = getattr(thing, '__name__', None) + return thing, name if isinstance(name, str) else None def render_doc(thing, title='Python Library Documentation: %s', forceload=0): """Render text documentation, given an object or a path to an object.""" @@ -1778,7 +1800,7 @@ Welcome to Python %s! This is the online help utility. If this is your first time using Python, you should definitely check out -the tutorial on the Internet at http://docs.python.org/tutorial/. +the tutorial on the Internet at http://docs.python.org/%s/tutorial/. Enter the name of any module, keyword, or topic to get help on writing Python programs and using Python modules. To quit this help utility and @@ -1788,7 +1810,7 @@ "keywords", or "topics". Each module also comes with a one-line summary of what it does; to list the modules whose summaries contain a given word such as "spam", type "modules spam". -''' % sys.version[:3]) +''' % tuple([sys.version[:3]]*2)) def list(self, items, columns=4, width=80): items = items[:] @@ -1966,10 +1988,11 @@ if modname[-9:] == '.__init__': modname = modname[:-9] + ' (package)' print modname, desc and '- ' + desc - try: import warnings - except ImportError: pass - else: warnings.filterwarnings('ignore') # ignore problems during import - ModuleScanner().run(callback, key) + def onerror(modname): + pass + with warnings.catch_warnings(): + warnings.filterwarnings('ignore') # ignore problems during import + ModuleScanner().run(callback, key, onerror=onerror) # --------------------------------------------------- web browser interface @@ -2041,14 +2064,10 @@ self.base.__init__(self, self.address, self.handler) def serve_until_quit(self): - import sys - if sys.platform.startswith('java'): - from select import cpython_compatible_select as select - else: - from select import select + import select self.quit = False while not self.quit: - rd, wr, ex = select([self.socket], [], [], 1) + rd, wr, ex = select.select([self.socket.fileno()], [], [], 1) if rd: self.handle_request() def server_activate(self): @@ -2061,7 +2080,7 @@ try: try: DocServer(port, callback).serve_until_quit() - except (KeyboardInterrupt, select.error): + except (KeyboardInterrupt, select.error): pass finally: if completer: completer() -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Thu Mar 21 23:37:04 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Thu, 21 Mar 2013 23:37:04 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Allow_AttributeError_in_Jyt?= =?utf-8?q?hon=2E?= Message-ID: <3ZX2tS6rdlzSTW@mail.python.org> http://hg.python.org/jython/rev/a07997d67bb2 changeset: 7094:a07997d67bb2 user: Frank Wierzbicki date: Thu Mar 21 15:36:58 2013 -0700 summary: Allow AttributeError in Jython. files: Lib/test/test_functools.py | 4 +++- 1 files changed, 3 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -117,13 +117,15 @@ self.assertRaises(ZeroDivisionError, self.thetype(f), 1, 0) self.assertRaises(ZeroDivisionError, self.thetype(f, y=0), 1) - @unittest.skip("FIXME: Not working in Jython.") def test_attributes(self): p = self.thetype(hex) try: del p.__dict__ except TypeError: pass + except AttributeError: + #In some cases Jython raises AttributeError here. + pass else: self.fail('partial object allowed __dict__ to be deleted') -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 17:56:06 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 22 Mar 2013 17:56:06 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Support_-s=2C_fix_up_test?= =?utf-8?b?X3NpdGUu?= Message-ID: <3ZXWGZ2GY7z7LjN@mail.python.org> http://hg.python.org/jython/rev/de22a47df837 changeset: 7095:de22a47df837 user: Frank Wierzbicki date: Fri Mar 22 09:55:58 2013 -0700 summary: Support -s, fix up test_site. files: Lib/test/test_site.py | 31 +++++++++++--------- src/org/python/util/jython.java | 5 ++- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -134,7 +134,8 @@ self.assertRegexpMatches(err_out.getvalue(), 'Traceback') self.assertRegexpMatches(err_out.getvalue(), 'ImportError') - @unittest.skipIf(is_jython, "FIXME: not on Jython yet.") + @unittest.skipIf(is_jython, "Jython does not raise an error for file " + "paths containing null characters") @unittest.skipIf(sys.platform == "win32", "Windows does not raise an " "error for file paths containing null characters") def test_addpackage_import_bad_pth_file(self): @@ -162,7 +163,6 @@ finally: pth_file.cleanup() - @unittest.skipIf(is_jython, "FIXME: not on Jython yet.") @unittest.skipUnless(site.ENABLE_USER_SITE, "requires access to PEP 370 " "user-site (site.ENABLE_USER_SITE)") def test_s_option(self): @@ -182,19 +182,22 @@ env=env) self.assertEqual(rc, 0) - env = os.environ.copy() - env["PYTHONNOUSERSITE"] = "1" - rc = subprocess.call([sys.executable, '-c', - 'import sys; sys.exit(%r in sys.path)' % usersite], - env=env) - self.assertEqual(rc, 0) + # XXX: These names are not supported. We may decide to support them as + # JYTHONNOUSERSITE and JYTHONUSERBASE in the future. + if not is_jython: + env = os.environ.copy() + env["PYTHONNOUSERSITE"] = "1" + rc = subprocess.call([sys.executable, '-c', + 'import sys; sys.exit(%r in sys.path)' % usersite], + env=env) + self.assertEqual(rc, 0) - env = os.environ.copy() - env["PYTHONUSERBASE"] = "/tmp" - rc = subprocess.call([sys.executable, '-c', - 'import sys, site; sys.exit(site.USER_BASE.startswith("/tmp"))'], - env=env) - self.assertEqual(rc, 1) + env = os.environ.copy() + env["PYTHONUSERBASE"] = "/tmp" + rc = subprocess.call([sys.executable, '-c', + 'import sys, site; sys.exit(site.USER_BASE.startswith("/tmp"))'], + env=env) + self.assertEqual(rc, 1) def test_getuserbase(self): site.USER_BASE = None diff --git a/src/org/python/util/jython.java b/src/org/python/util/jython.java --- a/src/org/python/util/jython.java +++ b/src/org/python/util/jython.java @@ -55,8 +55,7 @@ //"-O : optimize generated bytecode (a tad; also PYTHONOPTIMIZE=x)\n" + //"-OO : remove doc-strings in addition to the -O optimizations\n" + "-Q arg : division options: -Qold (default), -Qwarn, -Qwarnall, -Qnew\n" + - // XXX: support -s - //"-s : don't add user site directory to sys.path; also PYTHONNOUSERSITE\n" + + "-s : don't add user site directory to sys.path;\n" + // also PYTHONNOUSERSITE\n" + "-S : don't imply 'import site' on initialization\n" + //"-t : issue warnings about inconsistent tab usage (-tt: issue errors)\n" + "-u : unbuffered binary stdout and stderr\n" + // (also PYTHONUNBUFFERED=x)\n" + @@ -494,6 +493,8 @@ Options.verbose += 2; } else if (arg.equals("-vvv")) { Options.verbose +=3 ; + } else if (arg.equals("-s")) { + Options.no_user_site = true; } else if (arg.equals("-S")) { Options.importSite = false; } else if (arg.equals("-B")) { -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 18:04:06 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 22 Mar 2013 18:04:06 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=232005_threading=2EEvent?= =?utf-8?q?=2Ewait=28timeout=29_returns_boolean=2E_Thanks_Santoso_Wijaya!?= Message-ID: <3ZXWRp6bTKz7LjN@mail.python.org> http://hg.python.org/jython/rev/0d7595c0dafe changeset: 7096:0d7595c0dafe user: Frank Wierzbicki date: Fri Mar 22 10:03:57 2013 -0700 summary: #2005 threading.Event.wait(timeout) returns boolean. Thanks Santoso Wijaya! files: ACKNOWLEDGMENTS | 1 + Lib/threading.py | 3 +++ NEWS | 1 + 3 files changed, 5 insertions(+), 0 deletions(-) diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS --- a/ACKNOWLEDGMENTS +++ b/ACKNOWLEDGMENTS @@ -102,6 +102,7 @@ Andreas St?hrk Christian Klein Jezreel Ng + Santoso Wijaya Local Variables: mode: indented-text diff --git a/Lib/threading.py b/Lib/threading.py --- a/Lib/threading.py +++ b/Lib/threading.py @@ -416,5 +416,8 @@ try: if not self.__flag: self.__cond.wait(timeout) + # Issue 2005: Since CPython 2.7, threading.Event.wait(timeout) returns boolean. + # The function should return False if timeout is reached before the event is set. + return self.__flag finally: self.__cond.release() diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Jython 2.7b2 Bugs Fixed + - [ 2005 ] threading.Event object's wait([timeout]) function returns null instead of True/False. - [ 1926 ] Adjust MutableSet.pop test so we do not need to skip it - [ 2020 ] str.translate should delete characters in the second arg when table is None - [ 1753 ] zlib doesn't call end() on compress and decompress -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 18:54:07 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 22 Mar 2013 18:54:07 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=232027_Fix_handling_of_neg?= =?utf-8?q?atives_for_bin=2E_Thanks_Santoso_Wijaya!?= Message-ID: <3ZXXYW2MJgzRkL@mail.python.org> http://hg.python.org/jython/rev/e8b0f3987abd changeset: 7097:e8b0f3987abd user: Frank Wierzbicki date: Fri Mar 22 10:53:58 2013 -0700 summary: #2027 Fix handling of negatives for bin. Thanks Santoso Wijaya! files: Lib/test/test_builtin.py | 3 -- NEWS | 1 + src/org/python/core/PyInteger.java | 26 +++++++++++++---- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1538,7 +1538,6 @@ class DerivedFromStr(str): pass self.assertEqual(format(0, DerivedFromStr('10')), ' 0') - @unittest.skipIf(is_jython, "FIXME #1861: bin not implemented yet.") def test_bin(self): self.assertEqual(bin(0), '0b0') self.assertEqual(bin(1), '0b1') @@ -1548,8 +1547,6 @@ self.assertEqual(bin(-(2**65)), '-0b1' + '0' * 65) self.assertEqual(bin(-(2**65-1)), '-0b' + '1' * 65) - @unittest.skipIf(is_jython, - "FIXME #1861: bytearray not implemented in Jython yet") def test_bytearray_translate(self): x = bytearray("abc") self.assertRaises(ValueError, x.translate, "1", 1) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Jython 2.7b2 Bugs Fixed + - [ 2027 ] Discrepancy in bin(-num) output - [ 2005 ] threading.Event object's wait([timeout]) function returns null instead of True/False. - [ 1926 ] Adjust MutableSet.pop test so we do not need to skip it - [ 2020 ] str.translate should delete characters in the second arg when table is None diff --git a/src/org/python/core/PyInteger.java b/src/org/python/core/PyInteger.java --- a/src/org/python/core/PyInteger.java +++ b/src/org/python/core/PyInteger.java @@ -1096,14 +1096,28 @@ } if (spec.alternate) { - if (radix == 2) { - strValue = "0b" + strValue; - } else if (radix == 8) { - strValue = "0o" + strValue; - } else if (radix == 16) { - strValue = "0x" + strValue; + String strPrefix = ""; + switch (radix) { + case 2: + strPrefix = "0b"; + break; + case 8: + strPrefix = "0o"; + break; + case 16: + strPrefix = "0x"; + break; + } + + if (strValue.startsWith("-")) { + //assert (sign < 0); + if (!strPrefix.equals("")) + strValue = "-" + strPrefix + strValue.substring(1, strValue.length()); + } else { + strValue = strPrefix + strValue; } } + if (spec.type == 'X') { strValue = strValue.toUpperCase(); } -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 21:24:33 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Fri, 22 Mar 2013 21:24:33 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_=231862_cStringIO_now_takes?= =?utf-8?q?_array_arguments=2E_Thanks_Santoso_Wijaya!?= Message-ID: <3ZXbv56fNGz7LkY@mail.python.org> http://hg.python.org/jython/rev/98a58fb284f1 changeset: 7098:98a58fb284f1 user: Frank Wierzbicki date: Fri Mar 22 13:24:21 2013 -0700 summary: #1862 cStringIO now takes array arguments. Thanks Santoso Wijaya! files: Lib/test/test_StringIO.py | 1 - NEWS | 1 + src/org/python/modules/cStringIO.java | 14 ++++++++++++++ 3 files changed, 15 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_StringIO.py b/Lib/test/test_StringIO.py --- a/Lib/test/test_StringIO.py +++ b/Lib/test/test_StringIO.py @@ -128,7 +128,6 @@ class TestcStringIO(TestGenericStringIO): MODULE = cStringIO - @unittest.skipIf(test_support.is_jython, "FIXME #1862: not working in Jython") def test_array_support(self): # Issue #1730114: cStringIO should accept array objects a = array.array('B', [0,1,2]) diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ Jython 2.7b2 Bugs Fixed + - [ 1862 ] cStringIO does not support arrays as arguments - [ 2027 ] Discrepancy in bin(-num) output - [ 2005 ] threading.Event object's wait([timeout]) function returns null instead of True/False. - [ 1926 ] Adjust MutableSet.pop test so we do not need to skip it diff --git a/src/org/python/modules/cStringIO.java b/src/org/python/modules/cStringIO.java --- a/src/org/python/modules/cStringIO.java +++ b/src/org/python/modules/cStringIO.java @@ -12,6 +12,7 @@ package org.python.modules; import org.python.core.Py; +import org.python.core.PyArray; import org.python.core.PyIterator; import org.python.core.PyList; import org.python.core.PyObject; @@ -51,6 +52,15 @@ return new StringIO(buffer); } + /** + * Create a StringIO object, initialized by an array's byte stream. + * @param array The initial value, from an array. + * @return a new StringIO object. + */ + public static StringIO StringIO(PyArray array) { + return new StringIO(array); + } + /** * The StringIO object @@ -73,6 +83,10 @@ buf = new StringBuilder(buffer); } + public StringIO(PyArray array) { + buf = new StringBuilder(array.tostring()); + } + private void _complain_ifclosed() { if (closed) throw Py.ValueError("I/O operation on closed file"); -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 23:59:24 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 22 Mar 2013 23:59:24 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Update_test=5Fio_in_line_wi?= =?utf-8?q?th_lib_2=2E7?= Message-ID: <3ZXgKm68nHz7LkF@mail.python.org> http://hg.python.org/jython/rev/eaa8ba03b310 changeset: 7099:eaa8ba03b310 parent: 7085:039dba919d92 user: Jeff Allen date: Sun Mar 17 17:13:04 2013 +0000 summary: Update test_io in line with lib 2.7 Added skips for newly-failing tests, so now scoring fail/error/skip = 0/0/93 files: Lib/test/test_io.py | 173 +++++++++++++++++++++++++++++++- 1 files changed, 168 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -34,7 +34,9 @@ import errno from itertools import cycle, count from collections import deque +from UserList import UserList from test import test_support as support +import contextlib import codecs import io # subject of test @@ -587,6 +589,7 @@ raise IOError() f.flush = bad_flush self.assertRaises(IOError, f.close) # exception not swallowed + self.assertTrue(f.closed) def test_multi_close(self): f = self.open(support.TESTFN, "wb", buffering=0) @@ -608,6 +611,19 @@ self.assertEqual(rawio.read(2), None) self.assertEqual(rawio.read(2), b"") + def test_fileio_closefd(self): + # Issue #4841 + with self.open(__file__, 'rb') as f1, \ + self.open(__file__, 'rb') as f2: + fileio = self.FileIO(f1.fileno(), closefd=False) + # .__init__() must not close f1 + fileio.__init__(f2.fileno(), closefd=False) + f1.readline() + # .close() must not close f2 + fileio.close() + f2.readline() + + class CIOTest(IOTest): @unittest.skipIf(support.is_jython, "GC nondeterministic in Jython") @@ -752,6 +768,22 @@ raw.flush = bad_flush b = self.tp(raw) self.assertRaises(IOError, b.close) # exception not swallowed + self.assertTrue(b.closed) + + @unittest.skipIf(support.is_jython, "FIXME: by redefinition of closed") + def test_close_error_on_close(self): + raw = self.MockRawIO() + def bad_flush(): + raise IOError('flush') + def bad_close(): + raise IOError('close') + raw.close = bad_close + b = self.tp(raw) + b.flush = bad_flush + with self.assertRaises(IOError) as err: # exception not swallowed + b.close() + self.assertEqual(err.exception.args, ('close',)) + self.assertFalse(b.closed) def test_multi_close(self): raw = self.MockRawIO() @@ -769,6 +801,20 @@ buf.raw = x +class SizeofTest: + + @support.cpython_only + def test_sizeof(self): + bufsize1 = 4096 + bufsize2 = 8192 + rawio = self.MockRawIO() + bufio = self.tp(rawio, buffer_size=bufsize1) + size = sys.getsizeof(bufio) - bufsize1 + rawio = self.MockRawIO() + bufio = self.tp(rawio, buffer_size=bufsize2) + self.assertEqual(sys.getsizeof(bufio), size + bufsize2) + + class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): read_mode = "rb" @@ -952,7 +998,7 @@ "failed for {}: {} != 0".format(n, rawio._extraneous_reads)) -class CBufferedReaderTest(BufferedReaderTest): +class CBufferedReaderTest(BufferedReaderTest, SizeofTest): tp = io.BufferedReader def test_constructor(self): @@ -998,6 +1044,13 @@ support.gc_collect() self.assertTrue(wr() is None, wr) + @unittest.skipIf(support.is_jython, "FIXME: Jython does not mention type") + def test_args_error(self): + # Issue #17275 + with self.assertRaisesRegexp(TypeError, "BufferedReader"): + self.tp(io.BytesIO(), 1024, 1024, 1024) + + class PyBufferedReaderTest(BufferedReaderTest): tp = pyio.BufferedReader @@ -1138,6 +1191,29 @@ bufio.flush() self.assertEqual(b"abc", writer._write_stack[0]) + def test_writelines(self): + l = [b'ab', b'cd', b'ef'] + writer = self.MockRawIO() + bufio = self.tp(writer, 8) + bufio.writelines(l) + bufio.flush() + self.assertEqual(b''.join(writer._write_stack), b'abcdef') + + def test_writelines_userlist(self): + l = UserList([b'ab', b'cd', b'ef']) + writer = self.MockRawIO() + bufio = self.tp(writer, 8) + bufio.writelines(l) + bufio.flush() + self.assertEqual(b''.join(writer._write_stack), b'abcdef') + + @unittest.skipIf(support.is_jython, "FIXME: better type checking") + def test_writelines_error(self): + writer = self.MockRawIO() + bufio = self.tp(writer, 8) + self.assertRaises(TypeError, bufio.writelines, [1, 2, 3]) + self.assertRaises(TypeError, bufio.writelines, None) + @unittest.skipIf(support.is_jython, "GC nondeterministic in Jython") def test_destructor(self): writer = self.MockRawIO() @@ -1220,8 +1296,18 @@ DeprecationWarning)): self.tp(self.MockRawIO(), 8, 12) - -class CBufferedWriterTest(BufferedWriterTest): + def test_write_error_on_close(self): + raw = self.MockRawIO() + def bad_write(b): + raise IOError() + raw.write = bad_write + b = self.tp(raw) + b.write(b'spam') + self.assertRaises(IOError, b.close) # exception not swallowed + self.assertTrue(b.closed) + + +class CBufferedWriterTest(BufferedWriterTest, SizeofTest): tp = io.BufferedWriter def test_constructor(self): @@ -1260,6 +1346,12 @@ with self.open(support.TESTFN, "rb") as f: self.assertEqual(f.read(), b"123xxx") + @unittest.skipIf(support.is_jython, "FIXME: Jython does not mention type") + def test_args_error(self): + # Issue #17275 + with self.assertRaisesRegexp(TypeError, "BufferedWriter"): + self.tp(io.BytesIO(), 1024, 1024, 1024) + class PyBufferedWriterTest(BufferedWriterTest): tp = pyio.BufferedWriter @@ -1618,7 +1710,8 @@ self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n') -class CBufferedRandomTest(CBufferedReaderTest, CBufferedWriterTest, BufferedRandomTest): +class CBufferedRandomTest(CBufferedReaderTest, CBufferedWriterTest, + BufferedRandomTest, SizeofTest): tp = io.BufferedRandom def test_constructor(self): @@ -1635,6 +1728,13 @@ CBufferedReaderTest.test_garbage_collection(self) CBufferedWriterTest.test_garbage_collection(self) + @unittest.skipIf(support.is_jython, "FIXME: Jython does not mention type") + def test_args_error(self): + # Issue #17275 + with self.assertRaisesRegexp(TypeError, "BufferedRandom"): + self.tp(io.BytesIO(), 1024, 1024, 1024) + + class PyBufferedRandomTest(BufferedRandomTest): tp = pyio.BufferedRandom @@ -2233,6 +2333,29 @@ reads += c self.assertEqual(reads, "A"*127+"\nB") + def test_writelines(self): + l = ['ab', 'cd', 'ef'] + buf = self.BytesIO() + txt = self.TextIOWrapper(buf) + txt.writelines(l) + txt.flush() + self.assertEqual(buf.getvalue(), b'abcdef') + + def test_writelines_userlist(self): + l = UserList(['ab', 'cd', 'ef']) + buf = self.BytesIO() + txt = self.TextIOWrapper(buf) + txt.writelines(l) + txt.flush() + self.assertEqual(buf.getvalue(), b'abcdef') + + @unittest.skipIf(support.is_jython, "FIXME: better type checking") + def test_writelines_error(self): + txt = self.TextIOWrapper(self.BytesIO()) + self.assertRaises(TypeError, txt.writelines, [1, 2, 3]) + self.assertRaises(TypeError, txt.writelines, None) + self.assertRaises(TypeError, txt.writelines, b'abc') + def test_issue1395_1(self): txt = self.TextIOWrapper(self.BytesIO(self.testdata), encoding="ascii") @@ -2356,6 +2479,7 @@ raise IOError() txt.flush = bad_flush self.assertRaises(IOError, txt.close) # exception not swallowed + self.assertTrue(txt.closed) def test_multi_close(self): txt = self.TextIOWrapper(self.BytesIO(self.testdata), encoding="ascii") @@ -2370,6 +2494,41 @@ with self.assertRaises((AttributeError, TypeError)): txt.buffer = buf + @unittest.skipIf(support.is_jython, "FIXME: missing check?") + def test_read_nonbytes(self): + # Issue #17106 + # Crash when underlying read() returns non-bytes + class NonbytesStream(self.StringIO): + read1 = self.StringIO.read + class NonbytesStream(self.StringIO): + read1 = self.StringIO.read + t = self.TextIOWrapper(NonbytesStream('a')) + with self.maybeRaises(TypeError): + t.read(1) + t = self.TextIOWrapper(NonbytesStream('a')) + with self.maybeRaises(TypeError): + t.readline() + t = self.TextIOWrapper(NonbytesStream('a')) + self.assertEqual(t.read(), u'a') + + @unittest.skipIf(support.is_jython, "FIXME: missing check?") + def test_illegal_decoder(self): + # Issue #17106 + # Crash when decoder returns non-string + t = self.TextIOWrapper(self.BytesIO(b'aaaaaa'), newline='\n', + encoding='quopri_codec') + with self.maybeRaises(TypeError): + t.read(1) + t = self.TextIOWrapper(self.BytesIO(b'aaaaaa'), newline='\n', + encoding='quopri_codec') + with self.maybeRaises(TypeError): + t.readline() + t = self.TextIOWrapper(self.BytesIO(b'aaaaaa'), newline='\n', + encoding='quopri_codec') + with self.maybeRaises(TypeError): + t.read() + + class CTextIOWrapperTest(TextIOWrapperTest): def test_initialization(self): @@ -2416,9 +2575,13 @@ t2.buddy = t1 support.gc_collect() + maybeRaises = unittest.TestCase.assertRaises + class PyTextIOWrapperTest(TextIOWrapperTest): - pass + @contextlib.contextmanager + def maybeRaises(self, *args, **kwds): + yield class IncrementalNewlineDecoderTest(unittest.TestCase): -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 23:59:26 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 22 Mar 2013 23:59:26 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Error_during_io=2E*=2Eclose?= =?utf-8?q?=28=29_should_result_in_not_closed?= Message-ID: <3ZXgKp1RXTz7LkF@mail.python.org> http://hg.python.org/jython/rev/e6c8d0ee43d3 changeset: 7100:e6c8d0ee43d3 user: Jeff Allen date: Sun Mar 17 17:28:53 2013 +0000 summary: Error during io.*.close() should result in not closed test_io now down to 87 skips. files: Lib/_jyio.py | 8 ++++++-- Lib/test/test_io.py | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -227,7 +227,9 @@ def raw(self): return self._raw - # Jython difference: @property closed(self) inherited from _IOBase.__closed + @property + def closed(self): + return self.raw.closed # Jython difference: emulate C implementation CHECK_INITIALIZED. This is for # compatibility, to pass test.test_io.CTextIOWrapperTest.test_initialization. @@ -1108,7 +1110,9 @@ finally: self.buffer.close() - # Jython difference: @property closed(self) inherited from _IOBase.__closed + @property + def closed(self): + return self.buffer.closed # Jython difference: emulate C implementation CHECK_INITIALIZED. This is for # compatibility, to pass test.test_io.CTextIOWrapperTest.test_initialization. diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -770,7 +770,6 @@ self.assertRaises(IOError, b.close) # exception not swallowed self.assertTrue(b.closed) - @unittest.skipIf(support.is_jython, "FIXME: by redefinition of closed") def test_close_error_on_close(self): raw = self.MockRawIO() def bad_flush(): -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 23:59:27 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 22 Mar 2013 23:59:27 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_bytearray=2Eextend_fixed_to?= =?utf-8?q?_accept_only_iterable_arguments=2E?= Message-ID: <3ZXgKq3xzSz7LkV@mail.python.org> http://hg.python.org/jython/rev/cf6a2f673dc5 changeset: 7101:cf6a2f673dc5 user: Jeff Allen date: Sun Mar 17 18:45:09 2013 +0000 summary: bytearray.extend fixed to accept only iterable arguments. This was revealed in test_io, test_writelines_error() where otherwise writelines([1,2,3]) is acceptable. Test_io now down to 81 skips. files: Lib/test/test_io.py | 2 -- src/org/python/core/PyByteArray.java | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1206,7 +1206,6 @@ bufio.flush() self.assertEqual(b''.join(writer._write_stack), b'abcdef') - @unittest.skipIf(support.is_jython, "FIXME: better type checking") def test_writelines_error(self): writer = self.MockRawIO() bufio = self.tp(writer, 8) @@ -2348,7 +2347,6 @@ txt.flush() self.assertEqual(buf.getvalue(), b'abcdef') - @unittest.skipIf(support.is_jython, "FIXME: better type checking") def test_writelines_error(self): txt = self.TextIOWrapper(self.BytesIO()) self.assertRaises(TypeError, txt.writelines, [1, 2, 3]) diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java --- a/src/org/python/core/PyByteArray.java +++ b/src/org/python/core/PyByteArray.java @@ -1191,6 +1191,8 @@ @ExposedMethod(doc = BuiltinDocs.bytearray_extend_doc) final synchronized void bytearray_extend(PyObject o) { + // Raise TypeError if the argument is not iterable + o.__iter__(); // Use the general method, assigning to the crack at the end of the array. // Note this deals with all legitimate PyObject types including the case o==this. setslice(size, size, 1, o); -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 23:59:29 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 22 Mar 2013 23:59:29 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Check_buffer_and_decoder_ty?= =?utf-8?q?pes_in_=5Fjyio=2ETextIOWrapper?= Message-ID: <3ZXgKs0YPGz7LkY@mail.python.org> http://hg.python.org/jython/rev/7eab1d85d3c7 changeset: 7102:7eab1d85d3c7 user: Jeff Allen date: Thu Mar 21 07:55:57 2013 +0000 summary: Check buffer and decoder types in _jyio.TextIOWrapper We now pass the tests in test.test_io that require TextIOWrapper to verify that the buffer returns bytes, and the decoder returns unicode. We go one beyond CPython in requiring this of the unbounded read(), as not doing so appears to have been an oversight in textio.c. files: Lib/_jyio.py | 35 ++++++++++++++++++++++++++++---- Lib/test/test_io.py | 8 +++--- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -971,6 +971,20 @@ )[self.seennl] +def _check_decoded_chars(chars): + """Check decoder output is unicode""" + if not isinstance(chars, unicode): + raise TypeError("decoder should return a string result, not '%s'" % + type(chars)) + +def _check_buffered_bytes(b, context="read"): + """Check buffer has returned bytes""" + if not isinstance(b, str): + raise TypeError("underlying %s() should have returned a bytes object, not '%s'" % + (context, type(b))) + + + class TextIOWrapper(_TextIOBase): r"""Character and line based layer over a _BufferedIOBase object, buffer. @@ -1216,8 +1230,12 @@ # Read a chunk, decode it, and put the result in self._decoded_chars. input_chunk = self.buffer.read1(self._CHUNK_SIZE) + _check_buffered_bytes(input_chunk, "read1") + eof = not input_chunk - self._set_decoded_chars(self._decoder.decode(input_chunk, eof)) + decoded_chunk = self._decoder.decode(input_chunk, eof) + _check_decoded_chars(decoded_chunk) + self._set_decoded_chars(decoded_chunk) if self._telling: # At the snapshot point, len(dec_buffer) bytes before the read, @@ -1370,8 +1388,11 @@ if chars_to_skip: # Just like _read_chunk, feed the decoder and save a snapshot. input_chunk = self.buffer.read(bytes_to_feed) - self._set_decoded_chars( - self._decoder.decode(input_chunk, need_eof)) + _check_buffered_bytes(input_chunk) + decoded_chunk = self._decoder.decode(input_chunk, need_eof) + _check_decoded_chars(decoded_chunk) + self._set_decoded_chars(decoded_chunk) + self._snapshot = (dec_flags, input_chunk) # Skip chars_to_skip of the decoded characters. @@ -1403,8 +1424,12 @@ raise TypeError("an integer is required") if n < 0: # Read everything. - result = (self._get_decoded_chars() + - decoder.decode(self.buffer.read(), final=True)) + input_chunk = self.buffer.read() + # Jython difference: CPython textio.c omits: + _check_buffered_bytes(input_chunk) + decoded_chunk = decoder.decode(input_chunk, final=True) + _check_decoded_chars(decoded_chunk) + result = self._get_decoded_chars() + decoded_chunk self._set_decoded_chars('') self._snapshot = None return result diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2491,7 +2491,6 @@ with self.assertRaises((AttributeError, TypeError)): txt.buffer = buf - @unittest.skipIf(support.is_jython, "FIXME: missing check?") def test_read_nonbytes(self): # Issue #17106 # Crash when underlying read() returns non-bytes @@ -2506,9 +2505,10 @@ with self.maybeRaises(TypeError): t.readline() t = self.TextIOWrapper(NonbytesStream('a')) - self.assertEqual(t.read(), u'a') - - @unittest.skipIf(support.is_jython, "FIXME: missing check?") + with self.maybeRaises(TypeError): + # Jython difference: also detect the error in + t.read() + def test_illegal_decoder(self): # Issue #17106 # Crash when decoder returns non-string -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 23:59:30 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 22 Mar 2013 23:59:30 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_Skip_test=5Fargs=5Ferror=28?= =?utf-8?q?=29_until_Java_implementation?= Message-ID: <3ZXgKt38jhz7LkT@mail.python.org> http://hg.python.org/jython/rev/72cfa7952565 changeset: 7103:72cfa7952565 user: Jeff Allen date: Thu Mar 21 22:59:56 2013 +0000 summary: Skip test_args_error() until Java implementation The test expects the class name not __init__ to appear in the error message when the number or arguments is wrong. Defer this issue until there is a Java implementation, as the behaviour is a consequence of implementation in Python. files: Lib/test/test_io.py | 9 ++++++--- 1 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1043,7 +1043,8 @@ support.gc_collect() self.assertTrue(wr() is None, wr) - @unittest.skipIf(support.is_jython, "FIXME: Jython does not mention type") + @unittest.skipIf(support.is_jython, "FIXME: in the Java version with ArgParser") + # When implemented in Python, the error message is about __init__, even on CPython def test_args_error(self): # Issue #17275 with self.assertRaisesRegexp(TypeError, "BufferedReader"): @@ -1344,7 +1345,8 @@ with self.open(support.TESTFN, "rb") as f: self.assertEqual(f.read(), b"123xxx") - @unittest.skipIf(support.is_jython, "FIXME: Jython does not mention type") + @unittest.skipIf(support.is_jython, "FIXME: in the Java version with ArgParser") + # When implemented in Python, the error message is about __init__, even on CPython def test_args_error(self): # Issue #17275 with self.assertRaisesRegexp(TypeError, "BufferedWriter"): @@ -1726,7 +1728,8 @@ CBufferedReaderTest.test_garbage_collection(self) CBufferedWriterTest.test_garbage_collection(self) - @unittest.skipIf(support.is_jython, "FIXME: Jython does not mention type") + @unittest.skipIf(support.is_jython, "FIXME: in the Java version with ArgParser") + # When implemented in Python, the error message is about __init__, even on CPython def test_args_error(self): # Issue #17275 with self.assertRaisesRegexp(TypeError, "BufferedRandom"): -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Fri Mar 22 23:59:31 2013 From: jython-checkins at python.org (jeff.allen) Date: Fri, 22 Mar 2013 23:59:31 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_v2=2E7_test=5Fio_fixes?= Message-ID: <3ZXgKv6xNJz7LkV@mail.python.org> http://hg.python.org/jython/rev/efbbc2b78186 changeset: 7104:efbbc2b78186 parent: 7098:98a58fb284f1 parent: 7103:72cfa7952565 user: Jeff Allen date: Fri Mar 22 22:52:22 2013 +0000 summary: Merge v2.7 test_io fixes files: Lib/_jyio.py | 43 +++- Lib/test/test_io.py | 173 ++++++++++++++- src/org/python/core/PyByteArray.java | 2 + 3 files changed, 206 insertions(+), 12 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -227,7 +227,9 @@ def raw(self): return self._raw - # Jython difference: @property closed(self) inherited from _IOBase.__closed + @property + def closed(self): + return self.raw.closed # Jython difference: emulate C implementation CHECK_INITIALIZED. This is for # compatibility, to pass test.test_io.CTextIOWrapperTest.test_initialization. @@ -969,6 +971,20 @@ )[self.seennl] +def _check_decoded_chars(chars): + """Check decoder output is unicode""" + if not isinstance(chars, unicode): + raise TypeError("decoder should return a string result, not '%s'" % + type(chars)) + +def _check_buffered_bytes(b, context="read"): + """Check buffer has returned bytes""" + if not isinstance(b, str): + raise TypeError("underlying %s() should have returned a bytes object, not '%s'" % + (context, type(b))) + + + class TextIOWrapper(_TextIOBase): r"""Character and line based layer over a _BufferedIOBase object, buffer. @@ -1108,7 +1124,9 @@ finally: self.buffer.close() - # Jython difference: @property closed(self) inherited from _IOBase.__closed + @property + def closed(self): + return self.buffer.closed # Jython difference: emulate C implementation CHECK_INITIALIZED. This is for # compatibility, to pass test.test_io.CTextIOWrapperTest.test_initialization. @@ -1212,8 +1230,12 @@ # Read a chunk, decode it, and put the result in self._decoded_chars. input_chunk = self.buffer.read1(self._CHUNK_SIZE) + _check_buffered_bytes(input_chunk, "read1") + eof = not input_chunk - self._set_decoded_chars(self._decoder.decode(input_chunk, eof)) + decoded_chunk = self._decoder.decode(input_chunk, eof) + _check_decoded_chars(decoded_chunk) + self._set_decoded_chars(decoded_chunk) if self._telling: # At the snapshot point, len(dec_buffer) bytes before the read, @@ -1366,8 +1388,11 @@ if chars_to_skip: # Just like _read_chunk, feed the decoder and save a snapshot. input_chunk = self.buffer.read(bytes_to_feed) - self._set_decoded_chars( - self._decoder.decode(input_chunk, need_eof)) + _check_buffered_bytes(input_chunk) + decoded_chunk = self._decoder.decode(input_chunk, need_eof) + _check_decoded_chars(decoded_chunk) + self._set_decoded_chars(decoded_chunk) + self._snapshot = (dec_flags, input_chunk) # Skip chars_to_skip of the decoded characters. @@ -1399,8 +1424,12 @@ raise TypeError("an integer is required") if n < 0: # Read everything. - result = (self._get_decoded_chars() + - decoder.decode(self.buffer.read(), final=True)) + input_chunk = self.buffer.read() + # Jython difference: CPython textio.c omits: + _check_buffered_bytes(input_chunk) + decoded_chunk = decoder.decode(input_chunk, final=True) + _check_decoded_chars(decoded_chunk) + result = self._get_decoded_chars() + decoded_chunk self._set_decoded_chars('') self._snapshot = None return result diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -34,7 +34,9 @@ import errno from itertools import cycle, count from collections import deque +from UserList import UserList from test import test_support as support +import contextlib import codecs import io # subject of test @@ -587,6 +589,7 @@ raise IOError() f.flush = bad_flush self.assertRaises(IOError, f.close) # exception not swallowed + self.assertTrue(f.closed) def test_multi_close(self): f = self.open(support.TESTFN, "wb", buffering=0) @@ -608,6 +611,19 @@ self.assertEqual(rawio.read(2), None) self.assertEqual(rawio.read(2), b"") + def test_fileio_closefd(self): + # Issue #4841 + with self.open(__file__, 'rb') as f1, \ + self.open(__file__, 'rb') as f2: + fileio = self.FileIO(f1.fileno(), closefd=False) + # .__init__() must not close f1 + fileio.__init__(f2.fileno(), closefd=False) + f1.readline() + # .close() must not close f2 + fileio.close() + f2.readline() + + class CIOTest(IOTest): @unittest.skipIf(support.is_jython, "GC nondeterministic in Jython") @@ -752,6 +768,21 @@ raw.flush = bad_flush b = self.tp(raw) self.assertRaises(IOError, b.close) # exception not swallowed + self.assertTrue(b.closed) + + def test_close_error_on_close(self): + raw = self.MockRawIO() + def bad_flush(): + raise IOError('flush') + def bad_close(): + raise IOError('close') + raw.close = bad_close + b = self.tp(raw) + b.flush = bad_flush + with self.assertRaises(IOError) as err: # exception not swallowed + b.close() + self.assertEqual(err.exception.args, ('close',)) + self.assertFalse(b.closed) def test_multi_close(self): raw = self.MockRawIO() @@ -769,6 +800,20 @@ buf.raw = x +class SizeofTest: + + @support.cpython_only + def test_sizeof(self): + bufsize1 = 4096 + bufsize2 = 8192 + rawio = self.MockRawIO() + bufio = self.tp(rawio, buffer_size=bufsize1) + size = sys.getsizeof(bufio) - bufsize1 + rawio = self.MockRawIO() + bufio = self.tp(rawio, buffer_size=bufsize2) + self.assertEqual(sys.getsizeof(bufio), size + bufsize2) + + class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): read_mode = "rb" @@ -952,7 +997,7 @@ "failed for {}: {} != 0".format(n, rawio._extraneous_reads)) -class CBufferedReaderTest(BufferedReaderTest): +class CBufferedReaderTest(BufferedReaderTest, SizeofTest): tp = io.BufferedReader def test_constructor(self): @@ -998,6 +1043,14 @@ support.gc_collect() self.assertTrue(wr() is None, wr) + @unittest.skipIf(support.is_jython, "FIXME: in the Java version with ArgParser") + # When implemented in Python, the error message is about __init__, even on CPython + def test_args_error(self): + # Issue #17275 + with self.assertRaisesRegexp(TypeError, "BufferedReader"): + self.tp(io.BytesIO(), 1024, 1024, 1024) + + class PyBufferedReaderTest(BufferedReaderTest): tp = pyio.BufferedReader @@ -1138,6 +1191,28 @@ bufio.flush() self.assertEqual(b"abc", writer._write_stack[0]) + def test_writelines(self): + l = [b'ab', b'cd', b'ef'] + writer = self.MockRawIO() + bufio = self.tp(writer, 8) + bufio.writelines(l) + bufio.flush() + self.assertEqual(b''.join(writer._write_stack), b'abcdef') + + def test_writelines_userlist(self): + l = UserList([b'ab', b'cd', b'ef']) + writer = self.MockRawIO() + bufio = self.tp(writer, 8) + bufio.writelines(l) + bufio.flush() + self.assertEqual(b''.join(writer._write_stack), b'abcdef') + + def test_writelines_error(self): + writer = self.MockRawIO() + bufio = self.tp(writer, 8) + self.assertRaises(TypeError, bufio.writelines, [1, 2, 3]) + self.assertRaises(TypeError, bufio.writelines, None) + @unittest.skipIf(support.is_jython, "GC nondeterministic in Jython") def test_destructor(self): writer = self.MockRawIO() @@ -1220,8 +1295,18 @@ DeprecationWarning)): self.tp(self.MockRawIO(), 8, 12) - -class CBufferedWriterTest(BufferedWriterTest): + def test_write_error_on_close(self): + raw = self.MockRawIO() + def bad_write(b): + raise IOError() + raw.write = bad_write + b = self.tp(raw) + b.write(b'spam') + self.assertRaises(IOError, b.close) # exception not swallowed + self.assertTrue(b.closed) + + +class CBufferedWriterTest(BufferedWriterTest, SizeofTest): tp = io.BufferedWriter def test_constructor(self): @@ -1260,6 +1345,13 @@ with self.open(support.TESTFN, "rb") as f: self.assertEqual(f.read(), b"123xxx") + @unittest.skipIf(support.is_jython, "FIXME: in the Java version with ArgParser") + # When implemented in Python, the error message is about __init__, even on CPython + def test_args_error(self): + # Issue #17275 + with self.assertRaisesRegexp(TypeError, "BufferedWriter"): + self.tp(io.BytesIO(), 1024, 1024, 1024) + class PyBufferedWriterTest(BufferedWriterTest): tp = pyio.BufferedWriter @@ -1618,7 +1710,8 @@ self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n') -class CBufferedRandomTest(CBufferedReaderTest, CBufferedWriterTest, BufferedRandomTest): +class CBufferedRandomTest(CBufferedReaderTest, CBufferedWriterTest, + BufferedRandomTest, SizeofTest): tp = io.BufferedRandom def test_constructor(self): @@ -1635,6 +1728,14 @@ CBufferedReaderTest.test_garbage_collection(self) CBufferedWriterTest.test_garbage_collection(self) + @unittest.skipIf(support.is_jython, "FIXME: in the Java version with ArgParser") + # When implemented in Python, the error message is about __init__, even on CPython + def test_args_error(self): + # Issue #17275 + with self.assertRaisesRegexp(TypeError, "BufferedRandom"): + self.tp(io.BytesIO(), 1024, 1024, 1024) + + class PyBufferedRandomTest(BufferedRandomTest): tp = pyio.BufferedRandom @@ -2233,6 +2334,28 @@ reads += c self.assertEqual(reads, "A"*127+"\nB") + def test_writelines(self): + l = ['ab', 'cd', 'ef'] + buf = self.BytesIO() + txt = self.TextIOWrapper(buf) + txt.writelines(l) + txt.flush() + self.assertEqual(buf.getvalue(), b'abcdef') + + def test_writelines_userlist(self): + l = UserList(['ab', 'cd', 'ef']) + buf = self.BytesIO() + txt = self.TextIOWrapper(buf) + txt.writelines(l) + txt.flush() + self.assertEqual(buf.getvalue(), b'abcdef') + + def test_writelines_error(self): + txt = self.TextIOWrapper(self.BytesIO()) + self.assertRaises(TypeError, txt.writelines, [1, 2, 3]) + self.assertRaises(TypeError, txt.writelines, None) + self.assertRaises(TypeError, txt.writelines, b'abc') + def test_issue1395_1(self): txt = self.TextIOWrapper(self.BytesIO(self.testdata), encoding="ascii") @@ -2356,6 +2479,7 @@ raise IOError() txt.flush = bad_flush self.assertRaises(IOError, txt.close) # exception not swallowed + self.assertTrue(txt.closed) def test_multi_close(self): txt = self.TextIOWrapper(self.BytesIO(self.testdata), encoding="ascii") @@ -2370,6 +2494,41 @@ with self.assertRaises((AttributeError, TypeError)): txt.buffer = buf + def test_read_nonbytes(self): + # Issue #17106 + # Crash when underlying read() returns non-bytes + class NonbytesStream(self.StringIO): + read1 = self.StringIO.read + class NonbytesStream(self.StringIO): + read1 = self.StringIO.read + t = self.TextIOWrapper(NonbytesStream('a')) + with self.maybeRaises(TypeError): + t.read(1) + t = self.TextIOWrapper(NonbytesStream('a')) + with self.maybeRaises(TypeError): + t.readline() + t = self.TextIOWrapper(NonbytesStream('a')) + with self.maybeRaises(TypeError): + # Jython difference: also detect the error in + t.read() + + def test_illegal_decoder(self): + # Issue #17106 + # Crash when decoder returns non-string + t = self.TextIOWrapper(self.BytesIO(b'aaaaaa'), newline='\n', + encoding='quopri_codec') + with self.maybeRaises(TypeError): + t.read(1) + t = self.TextIOWrapper(self.BytesIO(b'aaaaaa'), newline='\n', + encoding='quopri_codec') + with self.maybeRaises(TypeError): + t.readline() + t = self.TextIOWrapper(self.BytesIO(b'aaaaaa'), newline='\n', + encoding='quopri_codec') + with self.maybeRaises(TypeError): + t.read() + + class CTextIOWrapperTest(TextIOWrapperTest): def test_initialization(self): @@ -2416,9 +2575,13 @@ t2.buddy = t1 support.gc_collect() + maybeRaises = unittest.TestCase.assertRaises + class PyTextIOWrapperTest(TextIOWrapperTest): - pass + @contextlib.contextmanager + def maybeRaises(self, *args, **kwds): + yield class IncrementalNewlineDecoderTest(unittest.TestCase): diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java --- a/src/org/python/core/PyByteArray.java +++ b/src/org/python/core/PyByteArray.java @@ -1191,6 +1191,8 @@ @ExposedMethod(doc = BuiltinDocs.bytearray_extend_doc) final synchronized void bytearray_extend(PyObject o) { + // Raise TypeError if the argument is not iterable + o.__iter__(); // Use the general method, assigning to the crack at the end of the array. // Note this deals with all legitimate PyObject types including the case o==this. setslice(size, size, 1, o); -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Mar 23 00:27:35 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 23 Mar 2013 00:27:35 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython=3A_pprint_skip_demoted_to_XXX_?= =?utf-8?q?from_FIXME_=28less_critical_in_my_mind=29=2E?= Message-ID: <3ZXgyH5zt6z7LkT@mail.python.org> http://hg.python.org/jython/rev/554ff974f691 changeset: 7105:554ff974f691 parent: 7098:98a58fb284f1 user: Frank Wierzbicki date: Fri Mar 22 16:26:45 2013 -0700 summary: pprint skip demoted to XXX from FIXME (less critical in my mind). files: Lib/test/test_pprint.py | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-) diff --git a/Lib/test/test_pprint.py b/Lib/test/test_pprint.py --- a/Lib/test/test_pprint.py +++ b/Lib/test/test_pprint.py @@ -204,7 +204,8 @@ others.should.not.be: like.this}""" self.assertEqual(DottedPrettyPrinter().pformat(o), exp) - @unittest.skip("FIXME: broken") + @unittest.skipIf(test.test_support.is_jython, + "XXX: depends on set repr order") def test_set_reprs(self): self.assertEqual(pprint.pformat(set()), 'set()') self.assertEqual(pprint.pformat(set(range(3))), 'set([0, 1, 2])') -- Repository URL: http://hg.python.org/jython From jython-checkins at python.org Sat Mar 23 00:27:37 2013 From: jython-checkins at python.org (frank.wierzbicki) Date: Sat, 23 Mar 2013 00:27:37 +0100 (CET) Subject: [Jython-checkins] =?utf-8?q?jython_=28merge_default_-=3E_default?= =?utf-8?q?=29=3A_Merge_forward=2E?= Message-ID: <3ZXgyK2hL7z7LkY@mail.python.org> http://hg.python.org/jython/rev/11776cd9765b changeset: 7106:11776cd9765b parent: 7105:554ff974f691 parent: 7104:efbbc2b78186 user: Frank Wierzbicki date: Fri Mar 22 16:27:17 2013 -0700 summary: Merge forward. files: Lib/_jyio.py | 43 +++- Lib/test/test_io.py | 173 ++++++++++++++- src/org/python/core/PyByteArray.java | 2 + 3 files changed, 206 insertions(+), 12 deletions(-) diff --git a/Lib/_jyio.py b/Lib/_jyio.py --- a/Lib/_jyio.py +++ b/Lib/_jyio.py @@ -227,7 +227,9 @@ def raw(self): return self._raw - # Jython difference: @property closed(self) inherited from _IOBase.__closed + @property + def closed(self): + return self.raw.closed # Jython difference: emulate C implementation CHECK_INITIALIZED. This is for # compatibility, to pass test.test_io.CTextIOWrapperTest.test_initialization. @@ -969,6 +971,20 @@ )[self.seennl] +def _check_decoded_chars(chars): + """Check decoder output is unicode""" + if not isinstance(chars, unicode): + raise TypeError("decoder should return a string result, not '%s'" % + type(chars)) + +def _check_buffered_bytes(b, context="read"): + """Check buffer has returned bytes""" + if not isinstance(b, str): + raise TypeError("underlying %s() should have returned a bytes object, not '%s'" % + (context, type(b))) + + + class TextIOWrapper(_TextIOBase): r"""Character and line based layer over a _BufferedIOBase object, buffer. @@ -1108,7 +1124,9 @@ finally: self.buffer.close() - # Jython difference: @property closed(self) inherited from _IOBase.__closed + @property + def closed(self): + return self.buffer.closed # Jython difference: emulate C implementation CHECK_INITIALIZED. This is for # compatibility, to pass test.test_io.CTextIOWrapperTest.test_initialization. @@ -1212,8 +1230,12 @@ # Read a chunk, decode it, and put the result in self._decoded_chars. input_chunk = self.buffer.read1(self._CHUNK_SIZE) + _check_buffered_bytes(input_chunk, "read1") + eof = not input_chunk - self._set_decoded_chars(self._decoder.decode(input_chunk, eof)) + decoded_chunk = self._decoder.decode(input_chunk, eof) + _check_decoded_chars(decoded_chunk) + self._set_decoded_chars(decoded_chunk) if self._telling: # At the snapshot point, len(dec_buffer) bytes before the read, @@ -1366,8 +1388,11 @@ if chars_to_skip: # Just like _read_chunk, feed the decoder and save a snapshot. input_chunk = self.buffer.read(bytes_to_feed) - self._set_decoded_chars( - self._decoder.decode(input_chunk, need_eof)) + _check_buffered_bytes(input_chunk) + decoded_chunk = self._decoder.decode(input_chunk, need_eof) + _check_decoded_chars(decoded_chunk) + self._set_decoded_chars(decoded_chunk) + self._snapshot = (dec_flags, input_chunk) # Skip chars_to_skip of the decoded characters. @@ -1399,8 +1424,12 @@ raise TypeError("an integer is required") if n < 0: # Read everything. - result = (self._get_decoded_chars() + - decoder.decode(self.buffer.read(), final=True)) + input_chunk = self.buffer.read() + # Jython difference: CPython textio.c omits: + _check_buffered_bytes(input_chunk) + decoded_chunk = decoder.decode(input_chunk, final=True) + _check_decoded_chars(decoded_chunk) + result = self._get_decoded_chars() + decoded_chunk self._set_decoded_chars('') self._snapshot = None return result diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -34,7 +34,9 @@ import errno from itertools import cycle, count from collections import deque +from UserList import UserList from test import test_support as support +import contextlib import codecs import io # subject of test @@ -587,6 +589,7 @@ raise IOError() f.flush = bad_flush self.assertRaises(IOError, f.close) # exception not swallowed + self.assertTrue(f.closed) def test_multi_close(self): f = self.open(support.TESTFN, "wb", buffering=0) @@ -608,6 +611,19 @@ self.assertEqual(rawio.read(2), None) self.assertEqual(rawio.read(2), b"") + def test_fileio_closefd(self): + # Issue #4841 + with self.open(__file__, 'rb') as f1, \ + self.open(__file__, 'rb') as f2: + fileio = self.FileIO(f1.fileno(), closefd=False) + # .__init__() must not close f1 + fileio.__init__(f2.fileno(), closefd=False) + f1.readline() + # .close() must not close f2 + fileio.close() + f2.readline() + + class CIOTest(IOTest): @unittest.skipIf(support.is_jython, "GC nondeterministic in Jython") @@ -752,6 +768,21 @@ raw.flush = bad_flush b = self.tp(raw) self.assertRaises(IOError, b.close) # exception not swallowed + self.assertTrue(b.closed) + + def test_close_error_on_close(self): + raw = self.MockRawIO() + def bad_flush(): + raise IOError('flush') + def bad_close(): + raise IOError('close') + raw.close = bad_close + b = self.tp(raw) + b.flush = bad_flush + with self.assertRaises(IOError) as err: # exception not swallowed + b.close() + self.assertEqual(err.exception.args, ('close',)) + self.assertFalse(b.closed) def test_multi_close(self): raw = self.MockRawIO() @@ -769,6 +800,20 @@ buf.raw = x +class SizeofTest: + + @support.cpython_only + def test_sizeof(self): + bufsize1 = 4096 + bufsize2 = 8192 + rawio = self.MockRawIO() + bufio = self.tp(rawio, buffer_size=bufsize1) + size = sys.getsizeof(bufio) - bufsize1 + rawio = self.MockRawIO() + bufio = self.tp(rawio, buffer_size=bufsize2) + self.assertEqual(sys.getsizeof(bufio), size + bufsize2) + + class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): read_mode = "rb" @@ -952,7 +997,7 @@ "failed for {}: {} != 0".format(n, rawio._extraneous_reads)) -class CBufferedReaderTest(BufferedReaderTest): +class CBufferedReaderTest(BufferedReaderTest, SizeofTest): tp = io.BufferedReader def test_constructor(self): @@ -998,6 +1043,14 @@ support.gc_collect() self.assertTrue(wr() is None, wr) + @unittest.skipIf(support.is_jython, "FIXME: in the Java version with ArgParser") + # When implemented in Python, the error message is about __init__, even on CPython + def test_args_error(self): + # Issue #17275 + with self.assertRaisesRegexp(TypeError, "BufferedReader"): + self.tp(io.BytesIO(), 1024, 1024, 1024) + + class PyBufferedReaderTest(BufferedReaderTest): tp = pyio.BufferedReader @@ -1138,6 +1191,28 @@ bufio.flush() self.assertEqual(b"abc", writer._write_stack[0]) + def test_writelines(self): + l = [b'ab', b'cd', b'ef'] + writer = self.MockRawIO() + bufio = self.tp(writer, 8) + bufio.writelines(l) + bufio.flush() + self.assertEqual(b''.join(writer._write_stack), b'abcdef') + + def test_writelines_userlist(self): + l = UserList([b'ab', b'cd', b'ef']) + writer = self.MockRawIO() + bufio = self.tp(writer, 8) + bufio.writelines(l) + bufio.flush() + self.assertEqual(b''.join(writer._write_stack), b'abcdef') + + def test_writelines_error(self): + writer = self.MockRawIO() + bufio = self.tp(writer, 8) + self.assertRaises(TypeError, bufio.writelines, [1, 2, 3]) + self.assertRaises(TypeError, bufio.writelines, None) + @unittest.skipIf(support.is_jython, "GC nondeterministic in Jython") def test_destructor(self): writer = self.MockRawIO() @@ -1220,8 +1295,18 @@ DeprecationWarning)): self.tp(self.MockRawIO(), 8, 12) - -class CBufferedWriterTest(BufferedWriterTest): + def test_write_error_on_close(self): + raw = self.MockRawIO() + def bad_write(b): + raise IOError() + raw.write = bad_write + b = self.tp(raw) + b.write(b'spam') + self.assertRaises(IOError, b.close) # exception not swallowed + self.assertTrue(b.closed) + + +class CBufferedWriterTest(BufferedWriterTest, SizeofTest): tp = io.BufferedWriter def test_constructor(self): @@ -1260,6 +1345,13 @@ with self.open(support.TESTFN, "rb") as f: self.assertEqual(f.read(), b"123xxx") + @unittest.skipIf(support.is_jython, "FIXME: in the Java version with ArgParser") + # When implemented in Python, the error message is about __init__, even on CPython + def test_args_error(self): + # Issue #17275 + with self.assertRaisesRegexp(TypeError, "BufferedWriter"): + self.tp(io.BytesIO(), 1024, 1024, 1024) + class PyBufferedWriterTest(BufferedWriterTest): tp = pyio.BufferedWriter @@ -1618,7 +1710,8 @@ self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n') -class CBufferedRandomTest(CBufferedReaderTest, CBufferedWriterTest, BufferedRandomTest): +class CBufferedRandomTest(CBufferedReaderTest, CBufferedWriterTest, + BufferedRandomTest, SizeofTest): tp = io.BufferedRandom def test_constructor(self): @@ -1635,6 +1728,14 @@ CBufferedReaderTest.test_garbage_collection(self) CBufferedWriterTest.test_garbage_collection(self) + @unittest.skipIf(support.is_jython, "FIXME: in the Java version with ArgParser") + # When implemented in Python, the error message is about __init__, even on CPython + def test_args_error(self): + # Issue #17275 + with self.assertRaisesRegexp(TypeError, "BufferedRandom"): + self.tp(io.BytesIO(), 1024, 1024, 1024) + + class PyBufferedRandomTest(BufferedRandomTest): tp = pyio.BufferedRandom @@ -2233,6 +2334,28 @@ reads += c self.assertEqual(reads, "A"*127+"\nB") + def test_writelines(self): + l = ['ab', 'cd', 'ef'] + buf = self.BytesIO() + txt = self.TextIOWrapper(buf) + txt.writelines(l) + txt.flush() + self.assertEqual(buf.getvalue(), b'abcdef') + + def test_writelines_userlist(self): + l = UserList(['ab', 'cd', 'ef']) + buf = self.BytesIO() + txt = self.TextIOWrapper(buf) + txt.writelines(l) + txt.flush() + self.assertEqual(buf.getvalue(), b'abcdef') + + def test_writelines_error(self): + txt = self.TextIOWrapper(self.BytesIO()) + self.assertRaises(TypeError, txt.writelines, [1, 2, 3]) + self.assertRaises(TypeError, txt.writelines, None) + self.assertRaises(TypeError, txt.writelines, b'abc') + def test_issue1395_1(self): txt = self.TextIOWrapper(self.BytesIO(self.testdata), encoding="ascii") @@ -2356,6 +2479,7 @@ raise IOError() txt.flush = bad_flush self.assertRaises(IOError, txt.close) # exception not swallowed + self.assertTrue(txt.closed) def test_multi_close(self): txt = self.TextIOWrapper(self.BytesIO(self.testdata), encoding="ascii") @@ -2370,6 +2494,41 @@ with self.assertRaises((AttributeError, TypeError)): txt.buffer = buf + def test_read_nonbytes(self): + # Issue #17106 + # Crash when underlying read() returns non-bytes + class NonbytesStream(self.StringIO): + read1 = self.StringIO.read + class NonbytesStream(self.StringIO): + read1 = self.StringIO.read + t = self.TextIOWrapper(NonbytesStream('a')) + with self.maybeRaises(TypeError): + t.read(1) + t = self.TextIOWrapper(NonbytesStream('a')) + with self.maybeRaises(TypeError): + t.readline() + t = self.TextIOWrapper(NonbytesStream('a')) + with self.maybeRaises(TypeError): + # Jython difference: also detect the error in + t.read() + + def test_illegal_decoder(self): + # Issue #17106 + # Crash when decoder returns non-string + t = self.TextIOWrapper(self.BytesIO(b'aaaaaa'), newline='\n', + encoding='quopri_codec') + with self.maybeRaises(TypeError): + t.read(1) + t = self.TextIOWrapper(self.BytesIO(b'aaaaaa'), newline='\n', + encoding='quopri_codec') + with self.maybeRaises(TypeError): + t.readline() + t = self.TextIOWrapper(self.BytesIO(b'aaaaaa'), newline='\n', + encoding='quopri_codec') + with self.maybeRaises(TypeError): + t.read() + + class CTextIOWrapperTest(TextIOWrapperTest): def test_initialization(self): @@ -2416,9 +2575,13 @@ t2.buddy = t1 support.gc_collect() + maybeRaises = unittest.TestCase.assertRaises + class PyTextIOWrapperTest(TextIOWrapperTest): - pass + @contextlib.contextmanager + def maybeRaises(self, *args, **kwds): + yield class IncrementalNewlineDecoderTest(unittest.TestCase): diff --git a/src/org/python/core/PyByteArray.java b/src/org/python/core/PyByteArray.java --- a/src/org/python/core/PyByteArray.java +++ b/src/org/python/core/PyByteArray.java @@ -1191,6 +1191,8 @@ @ExposedMethod(doc = BuiltinDocs.bytearray_extend_doc) final synchronized void bytearray_extend(PyObject o) { + // Raise TypeError if the argument is not iterable + o.__iter__(); // Use the general method, assigning to the crack at the end of the array. // Note this deals with all legitimate PyObject types including the case o==this. setslice(size, size, 1, o); -- Repository URL: http://hg.python.org/jython From pjenvey at underboss.org Mon Mar 25 01:02:42 2013 From: pjenvey at underboss.org (Philip Jenvey) Date: Sun, 24 Mar 2013 17:02:42 -0700 Subject: [Jython-checkins] jython: #2005 threading.Event.wait(timeout) returns boolean. Thanks Santoso Wijaya! In-Reply-To: <3ZXWRp6bTKz7LjN@mail.python.org> References: <3ZXWRp6bTKz7LjN@mail.python.org> Message-ID: On Fri, Mar 22, 2013 at 10:04 AM, frank.wierzbicki < jython-checkins at python.org> wrote: > http://hg.python.org/jython/rev/0d7595c0dafe > changeset: 7096:0d7595c0dafe > user: Frank Wierzbicki > date: Fri Mar 22 10:03:57 2013 -0700 > summary: > #2005 threading.Event.wait(timeout) returns boolean. Thanks Santoso > Wijaya! > > files: > ACKNOWLEDGMENTS | 1 + > Lib/threading.py | 3 +++ > NEWS | 1 + > 3 files changed, 5 insertions(+), 0 deletions(-) > > > diff --git a/ACKNOWLEDGMENTS b/ACKNOWLEDGMENTS > --- a/ACKNOWLEDGMENTS > +++ b/ACKNOWLEDGMENTS > @@ -102,6 +102,7 @@ > Andreas St?hrk > Christian Klein > Jezreel Ng > + Santoso Wijaya > > Local Variables: > mode: indented-text > diff --git a/Lib/threading.py b/Lib/threading.py > --- a/Lib/threading.py > +++ b/Lib/threading.py > @@ -416,5 +416,8 @@ > try: > if not self.__flag: > self.__cond.wait(timeout) > + # Issue 2005: Since CPython 2.7, > threading.Event.wait(timeout) returns boolean. > + # The function should return False if timeout is reached > before the event is set. > + return self.__flag > finally: > self.__cond.release() > I think self.__flag should be assigned to a local variable because it may have changed since the initial check. -- Philip Jenvey -------------- next part -------------- An HTML attachment was scrubbed... URL: