@@ -95,6 +95,22 @@ def test_errno_mapping(self):
9595 e = OSError (errcode , "Some message" )
9696 self .assertIs (type (e ), OSError , repr (e ))
9797
98+ def _defaults (self ):
99+ # The first errno listed for a class is the one it defaults to.
100+ defaults = {}
101+ for errcode , exc in self ._map .items ():
102+ defaults .setdefault (exc , errcode )
103+ return defaults
104+
105+ def test_default_errno (self ):
106+ for exc , errcode in self ._defaults ().items ():
107+ with self .subTest (exc = exc .__name__ ):
108+ self .assertEqual (exc .default_errno , errcode )
109+ e = exc ()
110+ self .assertEqual (e .errno , errcode )
111+ self .assertEqual (e .strerror , os .strerror (errcode ))
112+ self .assertEqual (e .args , (errcode , os .strerror (errcode )))
113+
98114 def test_try_except (self ):
99115 filename = "some_hopefully_non_existing_file"
100116
@@ -136,6 +152,80 @@ def test_posix_error(self):
136152 if os .name == "nt" :
137153 self .assertEqual (e .winerror , None )
138154
155+ def test_strerror_derived_from_errno (self ):
156+ e = FileNotFoundError ()
157+ self .assertEqual (e .errno , errno .ENOENT )
158+ self .assertEqual (e .strerror , os .strerror (errno .ENOENT ))
159+ # an explicit strerror wins over the one derived from errno
160+ e = FileNotFoundError ('not found' )
161+ self .assertEqual (e .errno , errno .ENOENT )
162+ self .assertEqual (e .strerror , 'not found' )
163+ self .assertEqual (e .args , (errno .ENOENT , 'not found' ))
164+ # ... including an explicit None
165+ e = FileNotFoundError (errno .ENOENT , None )
166+ self .assertIsNone (e .strerror )
167+
168+ @unittest .skipUnless (os .name == "nt" , "Windows-specific test" )
169+ def test_strerror_derived_from_winerror (self ):
170+ # the message of the Windows error code is more specific than the one
171+ # of the errno it is translated to
172+ import ctypes
173+ e = OSError (filename = "foo.txt" , winerror = 183 )
174+ self .assertEqual (e .errno , EEXIST )
175+ expected = ctypes .WinError (183 ).strerror .rstrip (" ." )
176+ self .assertEqual (e .strerror , expected )
177+ # a code which the system has no message for
178+ e = OSError (winerror = 99999 )
179+ self .assertEqual (e .errno , errno .EINVAL )
180+ self .assertEqual (e .strerror , "Windows Error 0x%x" % 99999 )
181+
182+ def test_strerror_not_derived_from_bogus_errno (self ):
183+ for code in 2 ** 31 , - 2 ** 31 - 1 , 2 ** 1000 , - 2 ** 1000 , 'x' :
184+ with self .subTest (default_errno = code ):
185+ cls = type ('E' , (OSError ,), {'default_errno' : code })
186+ e = cls ()
187+ self .assertEqual (e .errno , code )
188+ self .assertIsNone (e .strerror )
189+
190+ @unittest .skipUnless (os .name == "nt" , "Windows-specific test" )
191+ def test_strerror_not_derived_from_bogus_winerror (self ):
192+ # a winerror out of the C long range is rejected
193+ for code in 2 ** 31 , - 2 ** 31 - 1 , 2 ** 1000 , - 2 ** 1000 :
194+ with self .subTest (winerror = code ):
195+ self .assertRaises (OverflowError , OSError , winerror = code )
196+ # a winerror which is not an integer is not translated at all
197+ e = OSError (winerror = 'x' )
198+ self .assertIsNone (e .errno )
199+ self .assertIsNone (e .strerror )
200+ self .assertEqual (e .winerror , 'x' )
201+
202+ def test_keyword_arguments (self ):
203+ e = FileNotFoundError (filename = 'foo.txt' )
204+ self .assertEqual (e .errno , errno .ENOENT )
205+ self .assertEqual (e .strerror , os .strerror (errno .ENOENT ))
206+ self .assertEqual (e .filename , 'foo.txt' )
207+ self .assertIsNone (e .filename2 )
208+
209+ e = OSError ('cannot open' , filename = 'foo.txt' , filename2 = 'bar.txt' )
210+ self .assertIsNone (e .errno )
211+ self .assertEqual (e .strerror , 'cannot open' )
212+ self .assertEqual (e .filename , 'foo.txt' )
213+ self .assertEqual (e .filename2 , 'bar.txt' )
214+
215+ e = OSError (EEXIST , 'exists' , filename = 'foo.txt' )
216+ self .assertEqual (e .errno , EEXIST )
217+ self .assertEqual (e .filename , 'foo.txt' )
218+
219+ def test_keyword_argument_errors (self ):
220+ # errno and strerror are positional-only
221+ self .assertRaises (TypeError , OSError , errno = EEXIST )
222+ self .assertRaises (TypeError , OSError , strerror = 'exists' )
223+ # and cannot be given twice
224+ self .assertRaises (TypeError , OSError ,
225+ EEXIST , 'exists' , 'foo.txt' , filename = 'bar.txt' )
226+ # more than five arguments
227+ self .assertRaises (TypeError , OSError , 1 , 2 , 3 , 4 , 5 , 6 )
228+
139229 @unittest .skipUnless (os .name == "nt" , "Windows-specific test" )
140230 def test_errno_translation (self ):
141231 # ERROR_ALREADY_EXISTS (183) -> EEXIST
@@ -145,6 +235,13 @@ def test_errno_translation(self):
145235 self .assertEqual (e .args [0 ], EEXIST )
146236 self .assertEqual (e .strerror , "File already exists" )
147237 self .assertEqual (e .filename , "foo.txt" )
238+ # winerror can also be given by keyword
239+ e = OSError ("File already exists" , filename = "foo.txt" , winerror = 183 )
240+ self .assertEqual (e .winerror , 183 )
241+ self .assertEqual (e .errno , EEXIST )
242+ self .assertEqual (e .args , (EEXIST , "File already exists" , "foo.txt" , 183 ))
243+ self .assertEqual (e .strerror , "File already exists" )
244+ self .assertEqual (e .filename , "foo.txt" )
148245
149246 def test_blockingioerror (self ):
150247 args = ("a" , "b" , "c" , "d" , "e" )
@@ -162,6 +259,26 @@ def test_blockingioerror(self):
162259 with self .assertRaises (AttributeError ):
163260 e .characters_written
164261
262+ # characters_written can also be given by keyword
263+ e = BlockingIOError ("would block" , characters_written = 3 )
264+ self .assertEqual (e .strerror , "would block" )
265+ self .assertEqual (e .characters_written , 3 )
266+ # including when the class is chosen by errno
267+ e = OSError (errno .EAGAIN , "would block" , characters_written = 3 )
268+ self .assertIs (type (e ), BlockingIOError )
269+ self .assertEqual (e .characters_written , 3 )
270+ # but only for BlockingIOError
271+ for cls in OSError , FileNotFoundError :
272+ with self .subTest (cls = cls .__name__ ):
273+ self .assertRaises (TypeError , cls , characters_written = 3 )
274+ # and not together with a file name, which it is an alternative to
275+ self .assertRaises (TypeError , BlockingIOError ,
276+ filename = "foo.txt" , characters_written = 3 )
277+ self .assertRaises (TypeError , BlockingIOError ,
278+ errno .EAGAIN , "would block" , 3 , characters_written = 3 )
279+ # and it is keyword-only
280+ self .assertRaises (TypeError , BlockingIOError , 1 , 2 , 3 , 4 , 5 , 6 )
281+
165282
166283class ExplicitSubclassingTest (unittest .TestCase ):
167284
0 commit comments