1import builtins
2import os
3import select
4import socket
5import unittest
6import errno
7from errno import EEXIST
8
9
10class SubOSError(OSError):
11    pass
12
13class SubOSErrorWithInit(OSError):
14    def __init__(self, message, bar):
15        self.bar = bar
16        super().__init__(message)
17
18class SubOSErrorWithNew(OSError):
19    def __new__(cls, message, baz):
20        self = super().__new__(cls, message)
21        self.baz = baz
22        return self
23
24class SubOSErrorCombinedInitFirst(SubOSErrorWithInit, SubOSErrorWithNew):
25    pass
26
27class SubOSErrorCombinedNewFirst(SubOSErrorWithNew, SubOSErrorWithInit):
28    pass
29
30class SubOSErrorWithStandaloneInit(OSError):
31    def __init__(self):
32        pass
33
34
35class HierarchyTest(unittest.TestCase):
36
37    def test_builtin_errors(self):
38        self.assertEqual(OSError.__name__, 'OSError')
39        self.assertIs(IOError, OSError)
40        self.assertIs(EnvironmentError, OSError)
41
42    def test_socket_errors(self):
43        self.assertIs(socket.error, IOError)
44        self.assertIs(socket.gaierror.__base__, OSError)
45        self.assertIs(socket.herror.__base__, OSError)
46        self.assertIs(socket.timeout.__base__, OSError)
47
48    def test_select_error(self):
49        self.assertIs(select.error, OSError)
50
51    # mmap.error is tested in test_mmap
52
53    _pep_map = """
54        +-- BlockingIOError        EAGAIN, EALREADY, EWOULDBLOCK, EINPROGRESS
55        +-- ChildProcessError                                          ECHILD
56        +-- ConnectionError
57            +-- BrokenPipeError                              EPIPE, ESHUTDOWN
58            +-- ConnectionAbortedError                           ECONNABORTED
59            +-- ConnectionRefusedError                           ECONNREFUSED
60            +-- ConnectionResetError                               ECONNRESET
61        +-- FileExistsError                                            EEXIST
62        +-- FileNotFoundError                                          ENOENT
63        +-- InterruptedError                                            EINTR
64        +-- IsADirectoryError                                          EISDIR
65        +-- NotADirectoryError                                        ENOTDIR
66        +-- PermissionError                                     EACCES, EPERM
67        +-- ProcessLookupError                                          ESRCH
68        +-- TimeoutError                                            ETIMEDOUT
69    """
70    def _make_map(s):
71        _map = {}
72        for line in s.splitlines():
73            line = line.strip('+- ')
74            if not line:
75                continue
76            excname, _, errnames = line.partition(' ')
77            for errname in filter(None, errnames.strip().split(', ')):
78                _map[getattr(errno, errname)] = getattr(builtins, excname)
79        return _map
80    _map = _make_map(_pep_map)
81
82    def test_errno_mapping(self):
83        # The OSError constructor maps errnos to subclasses
84        # A sample test for the basic functionality
85        e = OSError(EEXIST, "Bad file descriptor")
86        self.assertIs(type(e), FileExistsError)
87        # Exhaustive testing
88        for errcode, exc in self._map.items():
89            e = OSError(errcode, "Some message")
90            self.assertIs(type(e), exc)
91        othercodes = set(errno.errorcode) - set(self._map)
92        for errcode in othercodes:
93            e = OSError(errcode, "Some message")
94            self.assertIs(type(e), OSError)
95
96    def test_try_except(self):
97        filename = "some_hopefully_non_existing_file"
98
99        # This checks that try .. except checks the concrete exception
100        # (FileNotFoundError) and not the base type specified when
101        # PyErr_SetFromErrnoWithFilenameObject was called.
102        # (it is therefore deliberate that it doesn't use assertRaises)
103        try:
104            open(filename)
105        except FileNotFoundError:
106            pass
107        else:
108            self.fail("should have raised a FileNotFoundError")
109
110        # Another test for PyErr_SetExcFromWindowsErrWithFilenameObject()
111        self.assertFalse(os.path.exists(filename))
112        try:
113            os.unlink(filename)
114        except FileNotFoundError:
115            pass
116        else:
117            self.fail("should have raised a FileNotFoundError")
118
119
120class AttributesTest(unittest.TestCase):
121
122    def test_windows_error(self):
123        if os.name == "nt":
124            self.assertIn('winerror', dir(OSError))
125        else:
126            self.assertNotIn('winerror', dir(OSError))
127
128    def test_posix_error(self):
129        e = OSError(EEXIST, "File already exists", "foo.txt")
130        self.assertEqual(e.errno, EEXIST)
131        self.assertEqual(e.args[0], EEXIST)
132        self.assertEqual(e.strerror, "File already exists")
133        self.assertEqual(e.filename, "foo.txt")
134        if os.name == "nt":
135            self.assertEqual(e.winerror, None)
136
137    @unittest.skipUnless(os.name == "nt", "Windows-specific test")
138    def test_errno_translation(self):
139        # ERROR_ALREADY_EXISTS (183) -> EEXIST
140        e = OSError(0, "File already exists", "foo.txt", 183)
141        self.assertEqual(e.winerror, 183)
142        self.assertEqual(e.errno, EEXIST)
143        self.assertEqual(e.args[0], EEXIST)
144        self.assertEqual(e.strerror, "File already exists")
145        self.assertEqual(e.filename, "foo.txt")
146
147    def test_blockingioerror(self):
148        args = ("a", "b", "c", "d", "e")
149        for n in range(6):
150            e = BlockingIOError(*args[:n])
151            with self.assertRaises(AttributeError):
152                e.characters_written
153        e = BlockingIOError("a", "b", 3)
154        self.assertEqual(e.characters_written, 3)
155        e.characters_written = 5
156        self.assertEqual(e.characters_written, 5)
157
158
159class ExplicitSubclassingTest(unittest.TestCase):
160
161    def test_errno_mapping(self):
162        # When constructing an OSError subclass, errno mapping isn't done
163        e = SubOSError(EEXIST, "Bad file descriptor")
164        self.assertIs(type(e), SubOSError)
165
166    def test_init_overridden(self):
167        e = SubOSErrorWithInit("some message", "baz")
168        self.assertEqual(e.bar, "baz")
169        self.assertEqual(e.args, ("some message",))
170
171    def test_init_kwdargs(self):
172        e = SubOSErrorWithInit("some message", bar="baz")
173        self.assertEqual(e.bar, "baz")
174        self.assertEqual(e.args, ("some message",))
175
176    def test_new_overridden(self):
177        e = SubOSErrorWithNew("some message", "baz")
178        self.assertEqual(e.baz, "baz")
179        self.assertEqual(e.args, ("some message",))
180
181    def test_new_kwdargs(self):
182        e = SubOSErrorWithNew("some message", baz="baz")
183        self.assertEqual(e.baz, "baz")
184        self.assertEqual(e.args, ("some message",))
185
186    def test_init_new_overridden(self):
187        e = SubOSErrorCombinedInitFirst("some message", "baz")
188        self.assertEqual(e.bar, "baz")
189        self.assertEqual(e.baz, "baz")
190        self.assertEqual(e.args, ("some message",))
191        e = SubOSErrorCombinedNewFirst("some message", "baz")
192        self.assertEqual(e.bar, "baz")
193        self.assertEqual(e.baz, "baz")
194        self.assertEqual(e.args, ("some message",))
195
196    def test_init_standalone(self):
197        # __init__ doesn't propagate to OSError.__init__ (see issue #15229)
198        e = SubOSErrorWithStandaloneInit()
199        self.assertEqual(e.args, ())
200        self.assertEqual(str(e), '')
201
202
203if __name__ == "__main__":
204    unittest.main()
205