diff options
author | Antoine Pitrou <solipsis@pitrou.net> | 2011-10-12 00:54:14 (GMT) |
---|---|---|
committer | Antoine Pitrou <solipsis@pitrou.net> | 2011-10-12 00:54:14 (GMT) |
commit | 6b4883dec0b7f6c5ede45dca861f5dc0e4ff2be7 (patch) | |
tree | e731290ba41ff9208385bebbe402282ecbbd682f /Lib | |
parent | 983b1434bda1819864fb5d82248f249358a4c22d (diff) | |
download | cpython-6b4883dec0b7f6c5ede45dca861f5dc0e4ff2be7.zip cpython-6b4883dec0b7f6c5ede45dca861f5dc0e4ff2be7.tar.gz cpython-6b4883dec0b7f6c5ede45dca861f5dc0e4ff2be7.tar.bz2 |
PEP 3151 / issue #12555: reworking the OS and IO exception hierarchy.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/_pyio.py | 12 | ||||
-rw-r--r-- | Lib/multiprocessing/connection.py | 8 | ||||
-rw-r--r-- | Lib/test/exception_hierarchy.txt | 21 | ||||
-rw-r--r-- | Lib/test/test_concurrent_futures.py | 14 | ||||
-rw-r--r-- | Lib/test/test_exceptions.py | 43 | ||||
-rw-r--r-- | Lib/test/test_http_cookiejar.py | 11 | ||||
-rw-r--r-- | Lib/test/test_io.py | 6 | ||||
-rw-r--r-- | Lib/test/test_mmap.py | 3 | ||||
-rw-r--r-- | Lib/test/test_pep3151.py | 128 | ||||
-rw-r--r-- | Lib/test/test_xml_etree.py | 8 | ||||
-rw-r--r-- | Lib/urllib/request.py | 2 |
11 files changed, 204 insertions, 52 deletions
diff --git a/Lib/_pyio.py b/Lib/_pyio.py index a9c31d5..0611bd6 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -23,16 +23,8 @@ DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes # defined in io.py. We don't use real inheritance though, because we don't # want to inherit the C implementations. - -class BlockingIOError(IOError): - - """Exception raised when I/O would block on a non-blocking I/O stream.""" - - def __init__(self, errno, strerror, characters_written=0): - super().__init__(errno, strerror) - if not isinstance(characters_written, int): - raise TypeError("characters_written must be a integer") - self.characters_written = characters_written +# Rebind for compatibility +BlockingIOError = BlockingIOError def open(file, mode="r", buffering=-1, encoding=None, errors=None, diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py index 13d3d77..0c96958 100644 --- a/Lib/multiprocessing/connection.py +++ b/Lib/multiprocessing/connection.py @@ -321,7 +321,7 @@ if win32: firstchunk = overlapped.getbuffer() assert lenfirstchunk == len(firstchunk) except IOError as e: - if e.errno == win32.ERROR_BROKEN_PIPE: + if e.winerror == win32.ERROR_BROKEN_PIPE: raise EOFError raise buf.write(firstchunk) @@ -669,7 +669,7 @@ if sys.platform == 'win32': try: win32.ConnectNamedPipe(handle, win32.NULL) except WindowsError as e: - if e.args[0] != win32.ERROR_PIPE_CONNECTED: + if e.winerror != win32.ERROR_PIPE_CONNECTED: raise return PipeConnection(handle) @@ -692,8 +692,8 @@ if sys.platform == 'win32': 0, win32.NULL, win32.OPEN_EXISTING, 0, win32.NULL ) except WindowsError as e: - if e.args[0] not in (win32.ERROR_SEM_TIMEOUT, - win32.ERROR_PIPE_BUSY) or _check_timeout(t): + if e.winerror not in (win32.ERROR_SEM_TIMEOUT, + win32.ERROR_PIPE_BUSY) or _check_timeout(t): raise else: break diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt index 5037b33..1c1f69f 100644 --- a/Lib/test/exception_hierarchy.txt +++ b/Lib/test/exception_hierarchy.txt @@ -11,11 +11,6 @@ BaseException +-- AssertionError +-- AttributeError +-- BufferError - +-- EnvironmentError - | +-- IOError - | +-- OSError - | +-- WindowsError (Windows) - | +-- VMSError (VMS) +-- EOFError +-- ImportError +-- LookupError @@ -24,6 +19,22 @@ BaseException +-- MemoryError +-- NameError | +-- UnboundLocalError + +-- OSError + | +-- BlockingIOError + | +-- ChildProcessError + | +-- ConnectionError + | | +-- BrokenPipeError + | | +-- ConnectionAbortedError + | | +-- ConnectionRefusedError + | | +-- ConnectionResetError + | +-- FileExistsError + | +-- FileNotFoundError + | +-- InterruptedError + | +-- IsADirectoryError + | +-- NotADirectoryError + | +-- PermissionError + | +-- ProcessLookupError + | +-- TimeoutError +-- ReferenceError +-- RuntimeError | +-- NotImplementedError diff --git a/Lib/test/test_concurrent_futures.py b/Lib/test/test_concurrent_futures.py index 78a9906..7522a54 100644 --- a/Lib/test/test_concurrent_futures.py +++ b/Lib/test/test_concurrent_futures.py @@ -34,7 +34,7 @@ PENDING_FUTURE = create_future(state=PENDING) RUNNING_FUTURE = create_future(state=RUNNING) CANCELLED_FUTURE = create_future(state=CANCELLED) CANCELLED_AND_NOTIFIED_FUTURE = create_future(state=CANCELLED_AND_NOTIFIED) -EXCEPTION_FUTURE = create_future(state=FINISHED, exception=IOError()) +EXCEPTION_FUTURE = create_future(state=FINISHED, exception=OSError()) SUCCESSFUL_FUTURE = create_future(state=FINISHED, result=42) @@ -501,7 +501,7 @@ class FutureTests(unittest.TestCase): '<Future at 0x[0-9a-f]+ state=cancelled>') self.assertRegex( repr(EXCEPTION_FUTURE), - '<Future at 0x[0-9a-f]+ state=finished raised IOError>') + '<Future at 0x[0-9a-f]+ state=finished raised OSError>') self.assertRegex( repr(SUCCESSFUL_FUTURE), '<Future at 0x[0-9a-f]+ state=finished returned int>') @@ -512,7 +512,7 @@ class FutureTests(unittest.TestCase): f2 = create_future(state=RUNNING) f3 = create_future(state=CANCELLED) f4 = create_future(state=CANCELLED_AND_NOTIFIED) - f5 = create_future(state=FINISHED, exception=IOError()) + f5 = create_future(state=FINISHED, exception=OSError()) f6 = create_future(state=FINISHED, result=5) self.assertTrue(f1.cancel()) @@ -566,7 +566,7 @@ class FutureTests(unittest.TestCase): CANCELLED_FUTURE.result, timeout=0) self.assertRaises(futures.CancelledError, CANCELLED_AND_NOTIFIED_FUTURE.result, timeout=0) - self.assertRaises(IOError, EXCEPTION_FUTURE.result, timeout=0) + self.assertRaises(OSError, EXCEPTION_FUTURE.result, timeout=0) self.assertEqual(SUCCESSFUL_FUTURE.result(timeout=0), 42) def test_result_with_success(self): @@ -605,7 +605,7 @@ class FutureTests(unittest.TestCase): self.assertRaises(futures.CancelledError, CANCELLED_AND_NOTIFIED_FUTURE.exception, timeout=0) self.assertTrue(isinstance(EXCEPTION_FUTURE.exception(timeout=0), - IOError)) + OSError)) self.assertEqual(SUCCESSFUL_FUTURE.exception(timeout=0), None) def test_exception_with_success(self): @@ -614,14 +614,14 @@ class FutureTests(unittest.TestCase): time.sleep(1) with f1._condition: f1._state = FINISHED - f1._exception = IOError() + f1._exception = OSError() f1._condition.notify_all() f1 = create_future(state=PENDING) t = threading.Thread(target=notification) t.start() - self.assertTrue(isinstance(f1.exception(timeout=5), IOError)) + self.assertTrue(isinstance(f1.exception(timeout=5), OSError)) @test.support.reap_threads def test_main(): diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 9be6958..a7683ac 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -46,8 +46,8 @@ class ExceptionTests(unittest.TestCase): fp.close() unlink(TESTFN) - self.raise_catch(IOError, "IOError") - self.assertRaises(IOError, open, 'this file does not exist', 'r') + self.raise_catch(OSError, "OSError") + self.assertRaises(OSError, open, 'this file does not exist', 'r') self.raise_catch(ImportError, "ImportError") self.assertRaises(ImportError, __import__, "undefined_module") @@ -192,11 +192,35 @@ class ExceptionTests(unittest.TestCase): except NameError: pass else: - self.assertEqual(str(WindowsError(1001)), "1001") - self.assertEqual(str(WindowsError(1001, "message")), - "[Error 1001] message") - self.assertEqual(WindowsError(1001, "message").errno, 22) - self.assertEqual(WindowsError(1001, "message").winerror, 1001) + self.assertIs(WindowsError, OSError) + self.assertEqual(str(OSError(1001)), "1001") + self.assertEqual(str(OSError(1001, "message")), + "[Errno 1001] message") + # POSIX errno (9 aka EBADF) is untranslated + w = OSError(9, 'foo', 'bar') + self.assertEqual(w.errno, 9) + self.assertEqual(w.winerror, None) + self.assertEqual(str(w), "[Errno 9] foo: 'bar'") + # ERROR_PATH_NOT_FOUND (win error 3) becomes ENOENT (2) + w = OSError(0, 'foo', 'bar', 3) + self.assertEqual(w.errno, 2) + self.assertEqual(w.winerror, 3) + self.assertEqual(w.strerror, 'foo') + self.assertEqual(w.filename, 'bar') + self.assertEqual(str(w), "[Error 3] foo: 'bar'") + # Unknown win error becomes EINVAL (22) + w = OSError(0, 'foo', None, 1001) + self.assertEqual(w.errno, 22) + self.assertEqual(w.winerror, 1001) + self.assertEqual(w.strerror, 'foo') + self.assertEqual(w.filename, None) + self.assertEqual(str(w), "[Error 1001] foo") + # Non-numeric "errno" + w = OSError('bar', 'foo') + self.assertEqual(w.errno, 'bar') + self.assertEqual(w.winerror, None) + self.assertEqual(w.strerror, 'foo') + self.assertEqual(w.filename, None) def testAttributes(self): # test that exception attributes are happy @@ -274,11 +298,12 @@ class ExceptionTests(unittest.TestCase): 'start' : 0, 'end' : 1}), ] try: + # More tests are in test_WindowsError exceptionList.append( (WindowsError, (1, 'strErrorStr', 'filenameStr'), {'args' : (1, 'strErrorStr'), - 'strerror' : 'strErrorStr', 'winerror' : 1, - 'errno' : 22, 'filename' : 'filenameStr'}) + 'strerror' : 'strErrorStr', 'winerror' : None, + 'errno' : 1, 'filename' : 'filenameStr'}) ) except NameError: pass diff --git a/Lib/test/test_http_cookiejar.py b/Lib/test/test_http_cookiejar.py index 41e0dfd..a35ec95 100644 --- a/Lib/test/test_http_cookiejar.py +++ b/Lib/test/test_http_cookiejar.py @@ -248,18 +248,19 @@ class FileCookieJarTests(unittest.TestCase): self.assertEqual(c._cookies["www.acme.com"]["/"]["boo"].value, None) def test_bad_magic(self): - # IOErrors (eg. file doesn't exist) are allowed to propagate + # OSErrors (eg. file doesn't exist) are allowed to propagate filename = test.support.TESTFN for cookiejar_class in LWPCookieJar, MozillaCookieJar: c = cookiejar_class() try: c.load(filename="for this test to work, a file with this " "filename should not exist") - except IOError as exc: - # exactly IOError, not LoadError - self.assertIs(exc.__class__, IOError) + except OSError as exc: + # an OSError subclass (likely FileNotFoundError), but not + # LoadError + self.assertIsNot(exc.__class__, LoadError) else: - self.fail("expected IOError for invalid filename") + self.fail("expected OSError for invalid filename") # Invalid contents of cookies file (eg. bad magic string) # causes a LoadError. try: diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index a9e3c37..0debc80 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -2643,12 +2643,6 @@ class MiscIOTest(unittest.TestCase): def test_blockingioerror(self): # Various BlockingIOError issues - self.assertRaises(TypeError, self.BlockingIOError) - self.assertRaises(TypeError, self.BlockingIOError, 1) - self.assertRaises(TypeError, self.BlockingIOError, 1, 2, 3, 4) - self.assertRaises(TypeError, self.BlockingIOError, 1, "", None) - b = self.BlockingIOError(1, "") - self.assertEqual(b.characters_written, 0) class C(str): pass c = C("") diff --git a/Lib/test/test_mmap.py b/Lib/test/test_mmap.py index 1cfcee6..2230028 100644 --- a/Lib/test/test_mmap.py +++ b/Lib/test/test_mmap.py @@ -563,8 +563,7 @@ class MmapTests(unittest.TestCase): f.close() def test_error(self): - self.assertTrue(issubclass(mmap.error, EnvironmentError)) - self.assertIn("mmap.error", str(mmap.error)) + self.assertIs(mmap.error, OSError) def test_io_methods(self): data = b"0123456789" diff --git a/Lib/test/test_pep3151.py b/Lib/test/test_pep3151.py new file mode 100644 index 0000000..9d92425 --- /dev/null +++ b/Lib/test/test_pep3151.py @@ -0,0 +1,128 @@ +import builtins +import os +import select +import socket +import sys +import unittest +import errno +from errno import EEXIST + +from test import support + +class SubOSError(OSError): + pass + + +class HierarchyTest(unittest.TestCase): + + def test_builtin_errors(self): + self.assertEqual(OSError.__name__, 'OSError') + self.assertIs(IOError, OSError) + self.assertIs(EnvironmentError, OSError) + + def test_socket_errors(self): + self.assertIs(socket.error, IOError) + self.assertIs(socket.gaierror.__base__, OSError) + self.assertIs(socket.herror.__base__, OSError) + self.assertIs(socket.timeout.__base__, OSError) + + def test_select_error(self): + self.assertIs(select.error, OSError) + + # mmap.error is tested in test_mmap + + _pep_map = """ + +-- BlockingIOError EAGAIN, EALREADY, EWOULDBLOCK, EINPROGRESS + +-- ChildProcessError ECHILD + +-- ConnectionError + +-- BrokenPipeError EPIPE, ESHUTDOWN + +-- ConnectionAbortedError ECONNABORTED + +-- ConnectionRefusedError ECONNREFUSED + +-- ConnectionResetError ECONNRESET + +-- FileExistsError EEXIST + +-- FileNotFoundError ENOENT + +-- InterruptedError EINTR + +-- IsADirectoryError EISDIR + +-- NotADirectoryError ENOTDIR + +-- PermissionError EACCES, EPERM + +-- ProcessLookupError ESRCH + +-- TimeoutError ETIMEDOUT + """ + def _make_map(s): + _map = {} + for line in s.splitlines(): + line = line.strip('+- ') + if not line: + continue + excname, _, errnames = line.partition(' ') + for errname in filter(None, errnames.strip().split(', ')): + _map[getattr(errno, errname)] = getattr(builtins, excname) + return _map + _map = _make_map(_pep_map) + + def test_errno_mapping(self): + # The OSError constructor maps errnos to subclasses + # A sample test for the basic functionality + e = OSError(EEXIST, "Bad file descriptor") + self.assertIs(type(e), FileExistsError) + # Exhaustive testing + for errcode, exc in self._map.items(): + e = OSError(errcode, "Some message") + self.assertIs(type(e), exc) + othercodes = set(errno.errorcode) - set(self._map) + for errcode in othercodes: + e = OSError(errcode, "Some message") + self.assertIs(type(e), OSError) + + def test_OSError_subclass_mapping(self): + # When constructing an OSError subclass, errno mapping isn't done + e = SubOSError(EEXIST, "Bad file descriptor") + self.assertIs(type(e), SubOSError) + + +class AttributesTest(unittest.TestCase): + + def test_windows_error(self): + if os.name == "nt": + self.assertIn('winerror', dir(OSError)) + else: + self.assertNotIn('winerror', dir(OSError)) + + def test_posix_error(self): + e = OSError(EEXIST, "File already exists", "foo.txt") + self.assertEqual(e.errno, EEXIST) + self.assertEqual(e.args[0], EEXIST) + self.assertEqual(e.strerror, "File already exists") + self.assertEqual(e.filename, "foo.txt") + if os.name == "nt": + self.assertEqual(e.winerror, None) + + @unittest.skipUnless(os.name == "nt", "Windows-specific test") + def test_errno_translation(self): + # ERROR_ALREADY_EXISTS (183) -> EEXIST + e = OSError(0, "File already exists", "foo.txt", 183) + self.assertEqual(e.winerror, 183) + self.assertEqual(e.errno, EEXIST) + self.assertEqual(e.args[0], EEXIST) + self.assertEqual(e.strerror, "File already exists") + self.assertEqual(e.filename, "foo.txt") + + def test_blockingioerror(self): + args = ("a", "b", "c", "d", "e") + for n in range(6): + e = BlockingIOError(*args[:n]) + with self.assertRaises(AttributeError): + e.characters_written + e = BlockingIOError("a", "b", 3) + self.assertEqual(e.characters_written, 3) + e.characters_written = 5 + self.assertEqual(e.characters_written, 5) + + # XXX VMSError not tested + + +def test_main(): + support.run_unittest(__name__) + +if __name__=="__main__": + test_main() diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 40c2291..4dd8d22 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -1339,7 +1339,7 @@ def xinclude_loader(href, parse="xml", encoding=None): try: data = XINCLUDE[href] except KeyError: - raise IOError("resource not found") + raise OSError("resource not found") if parse == "xml": from xml.etree.ElementTree import XML return XML(data) @@ -1404,7 +1404,7 @@ def xinclude(): >>> document = xinclude_loader("C5.xml") >>> ElementInclude.include(document, xinclude_loader) Traceback (most recent call last): - IOError: resource not found + OSError: resource not found >>> # print(serialize(document)) # C5 """ @@ -1611,7 +1611,7 @@ def bug_xmltoolkit55(): class ExceptionFile: def read(self, x): - raise IOError + raise OSError def xmltoolkit60(): """ @@ -1619,7 +1619,7 @@ def xmltoolkit60(): Handle crash in stream source. >>> tree = ET.parse(ExceptionFile()) Traceback (most recent call last): - IOError + OSError """ diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index 671ab68..a947608 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -1547,6 +1547,8 @@ class URLopener: return getattr(self, name)(url) else: return getattr(self, name)(url, data) + except HTTPError: + raise except socket.error as msg: raise IOError('socket error', msg).with_traceback(sys.exc_info()[2]) |