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, OSError)
44        self.assertIs(socket.gaierror.__base__, OSError)
45        self.assertIs(socket.herror.__base__, OSError)
46        self.assertIs(socket.timeout, TimeoutError)
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, ENOTCAPABLE
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                if errname == "ENOTCAPABLE" and not hasattr(errno, errname):
79                    continue
80                _map[getattr(errno, errname)] = getattr(builtins, excname)
81        return _map
82    _map = _make_map(_pep_map)
83
84    def test_errno_mapping(self):
85        # The OSError constructor maps errnos to subclasses
86        # A sample test for the basic functionality
87        e = OSError(EEXIST, "Bad file descriptor")
88        self.assertIs(type(e), FileExistsError)
89        # Exhaustive testing
90        for errcode, exc in self._map.items():
91            e = OSError(errcode, "Some message")
92            self.assertIs(type(e), exc)
93        othercodes = set(errno.errorcode) - set(self._map)
94        for errcode in othercodes:
95            e = OSError(errcode, "Some message")
96            self.assertIs(type(e), OSError, repr(e))
97
98    def test_try_except(self):
99        filename = "some_hopefully_non_existing_file"
100
101        # This checks that try .. except checks the concrete exception
102        # (FileNotFoundError) and not the base type specified when
103        # PyErr_SetFromErrnoWithFilenameObject was called.
104        # (it is therefore deliberate that it doesn't use assertRaises)
105        try:
106            open(filename)
107        except FileNotFoundError:
108            pass
109        else:
110            self.fail("should have raised a FileNotFoundError")
111
112        # Another test for PyErr_SetExcFromWindowsErrWithFilenameObject()
113        self.assertFalse(os.path.exists(filename))
114        try:
115            os.unlink(filename)
116        except FileNotFoundError:
117            pass
118        else:
119            self.fail("should have raised a FileNotFoundError")
120
121
122class AttributesTest(unittest.TestCase):
123
124    def test_windows_error(self):
125        if os.name == "nt":
126            self.assertIn('winerror', dir(OSError))
127        else:
128            self.assertNotIn('winerror', dir(OSError))
129
130    def test_posix_error(self):
131        e = OSError(EEXIST, "File already exists", "foo.txt")
132        self.assertEqual(e.errno, EEXIST)
133        self.assertEqual(e.args[0], EEXIST)
134        self.assertEqual(e.strerror, "File already exists")
135        self.assertEqual(e.filename, "foo.txt")
136        if os.name == "nt":
137            self.assertEqual(e.winerror, None)
138
139    @unittest.skipUnless(os.name == "nt", "Windows-specific test")
140    def test_errno_translation(self):
141        # ERROR_ALREADY_EXISTS (183) -> EEXIST
142        e = OSError(0, "File already exists", "foo.txt", 183)
143        self.assertEqual(e.winerror, 183)
144        self.assertEqual(e.errno, EEXIST)
145        self.assertEqual(e.args[0], EEXIST)
146        self.assertEqual(e.strerror, "File already exists")
147        self.assertEqual(e.filename, "foo.txt")
148
149    def test_blockingioerror(self):
150        args = ("a", "b", "c", "d", "e")
151        for n in range(6):
152            e = BlockingIOError(*args[:n])
153            with self.assertRaises(AttributeError):
154                e.characters_written
155            with self.assertRaises(AttributeError):
156                del e.characters_written
157        e = BlockingIOError("a", "b", 3)
158        self.assertEqual(e.characters_written, 3)
159        e.characters_written = 5
160        self.assertEqual(e.characters_written, 5)
161        del e.characters_written
162        with self.assertRaises(AttributeError):
163            e.characters_written
164
165
166class ExplicitSubclassingTest(unittest.TestCase):
167
168    def test_errno_mapping(self):
169        # When constructing an OSError subclass, errno mapping isn't done
170        e = SubOSError(EEXIST, "Bad file descriptor")
171        self.assertIs(type(e), SubOSError)
172
173    def test_init_overridden(self):
174        e = SubOSErrorWithInit("some message", "baz")
175        self.assertEqual(e.bar, "baz")
176        self.assertEqual(e.args, ("some message",))
177
178    def test_init_kwdargs(self):
179        e = SubOSErrorWithInit("some message", bar="baz")
180        self.assertEqual(e.bar, "baz")
181        self.assertEqual(e.args, ("some message",))
182
183    def test_new_overridden(self):
184        e = SubOSErrorWithNew("some message", "baz")
185        self.assertEqual(e.baz, "baz")
186        self.assertEqual(e.args, ("some message",))
187
188    def test_new_kwdargs(self):
189        e = SubOSErrorWithNew("some message", baz="baz")
190        self.assertEqual(e.baz, "baz")
191        self.assertEqual(e.args, ("some message",))
192
193    def test_init_new_overridden(self):
194        e = SubOSErrorCombinedInitFirst("some message", "baz")
195        self.assertEqual(e.bar, "baz")
196        self.assertEqual(e.baz, "baz")
197        self.assertEqual(e.args, ("some message",))
198        e = SubOSErrorCombinedNewFirst("some message", "baz")
199        self.assertEqual(e.bar, "baz")
200        self.assertEqual(e.baz, "baz")
201        self.assertEqual(e.args, ("some message",))
202
203    def test_init_standalone(self):
204        # __init__ doesn't propagate to OSError.__init__ (see issue #15229)
205        e = SubOSErrorWithStandaloneInit()
206        self.assertEqual(e.args, ())
207        self.assertEqual(str(e), '')
208
209
210if __name__ == "__main__":
211    unittest.main()
212