PEP 3151 / issue #12555: reworking the OS and IO exception hierarchy.
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()