Skip to content

Commit 15431c6

Browse files
authored
Fix exception mapping on OSX (#1506)
* Define test decorator expectedFailureIf * Fix exception mapping on OSX
1 parent 2f10037 commit 15431c6

File tree

4 files changed

+66
-16
lines changed

4 files changed

+66
-16
lines changed

Src/IronPython/Lib/iptest/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# The .NET Foundation licenses this file to you under the Apache 2.0 License.
33
# See the LICENSE file in the project root for more information.
44

5-
from .ipunittest import IronPythonTestCase, stdout_trapper, stderr_trapper, path_modifier, retryOnFailure, run_test, skipUnlessIronPython, source_root
5+
from .ipunittest import IronPythonTestCase, stdout_trapper, stderr_trapper, path_modifier, retryOnFailure, run_test, skipUnlessIronPython, source_root, expectedFailureIf
66
from .test_env import *
77
from .type_util import *
88
from .misc_util import ip_supported_encodings

Src/IronPython/Lib/iptest/ipunittest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ def skipUnlessIronPython():
5353
"""Skips the test unless currently running on IronPython"""
5454
return unittest.skipUnless(is_cli, 'IronPython specific test')
5555

56+
def expectedFailureIf(condition):
57+
"""The test is marked as an expectedFailure if the condition is satisfied."""
58+
def wrapper(func):
59+
if condition:
60+
return unittest.expectedFailure(func)
61+
else:
62+
return func
63+
return wrapper
64+
5665
MAX_FAILURE_RETRY = 3
5766
def retryOnFailure(f, times=MAX_FAILURE_RETRY, *args, **kwargs):
5867
'''

Src/IronPython/Runtime/Exceptions/PythonExceptions.cs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public partial class _OSError {
129129
errno = WinErrorToErrno(winerror);
130130
}
131131
}
132-
cls = ErrnoToPythonType((Error)errno);
132+
cls = ErrnoToPythonType(ErrnoToErrorEnum(errno));
133133
}
134134
return Activator.CreateInstance(cls.UnderlyingSystemType, cls);
135135
}
@@ -183,25 +183,26 @@ public override void __init__(params object[] args) {
183183
}
184184

185185
private enum Error {
186+
UNSPECIFIED = -1,
186187
EPERM = 1,
187188
ENOENT = 2,
188189
ESRCH = 3,
189190
EINTR = 4,
190191
ECHILD = 10,
191-
EAGAIN = 11,
192+
EAGAIN = 11, // 35 on OSX
192193
EACCES = 13,
193194
EEXIST = 17,
194195
ENOTDIR = 20,
195196
EISDIR = 21,
196197
EPIPE = 32,
197198
// Linux
198-
ECONNABORTED = 103,
199-
ECONNRESET = 104,
200-
ESHUTDOWN = 108,
201-
ETIMEDOUT = 110,
202-
ECONNREFUSED = 111,
203-
EALREADY = 114,
204-
EINPROGRESS = 115,
199+
ECONNABORTED = 103, // 53 on OSX
200+
ECONNRESET = 104, // 54 on OSX
201+
ESHUTDOWN = 108, // 58 on OSX
202+
ETIMEDOUT = 110, // 60 on OSX
203+
ECONNREFUSED = 111, // 61 on OSX
204+
EALREADY = 114, // 37 on OSX
205+
EINPROGRESS = 115, // 36 on OSX
205206
// Windows
206207
WSAEWOULDBLOCK = 10035,
207208
WSAEINPROGRESS = 10036,
@@ -213,6 +214,14 @@ private enum Error {
213214
WSAECONNREFUSED = 10061,
214215
}
215216

217+
private static Error ErrnoToErrorEnum(int errno) {
218+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
219+
if (errno == 11) return Error.UNSPECIFIED; // EAGAIN on Linux/Windows but EDEADLK on OSX, which is not being remapped
220+
if (errno >= 35) errno += 10000; // add WSABASEERR to map to Windows error range
221+
}
222+
return (Error)errno;
223+
}
224+
216225
private static PythonType ErrnoToPythonType(Error errno) {
217226
var res = errno switch
218227
{
@@ -229,10 +238,10 @@ private static PythonType ErrnoToPythonType(Error errno) {
229238
Error.EPIPE => BrokenPipeError,
230239
_ => null
231240
};
232-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
241+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
233242
res ??= errno switch
234243
{
235-
// Windows
244+
// Windows or remapped OSX
236245
Error.WSAEWOULDBLOCK => BlockingIOError,
237246
Error.WSAEINPROGRESS => BlockingIOError,
238247
Error.WSAEALREADY => BlockingIOError,
@@ -243,8 +252,7 @@ private static PythonType ErrnoToPythonType(Error errno) {
243252
Error.WSAECONNREFUSED => ConnectionRefusedError,
244253
_ => null
245254
};
246-
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
247-
// TODO: verify that these are the same on OSX
255+
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
248256
res ??= errno switch
249257
{
250258
// Linux

Tests/test_exceptions.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66
import unittest
77

8-
from iptest import IronPythonTestCase, is_cli, is_netcoreapp, run_test, skipUnlessIronPython
8+
from iptest import IronPythonTestCase, is_cli, is_netcoreapp, run_test, skipUnlessIronPython, expectedFailureIf
99

1010
class CP35300_Derived(EnvironmentError):
1111
def __init__(self, *args, **kwargs):
@@ -828,7 +828,7 @@ def __new__(cls, *args): return 42
828828
with self.assertRaises(TypeError): # TypeError: exceptions must derive from BaseException
829829
raise MyException('abc')
830830

831-
@unittest.expectedFailure # https://github.com/IronLanguages/ironpython3/issues/876
831+
@expectedFailureIf(is_cli) # https://github.com/IronLanguages/ironpython3/issues/876
832832
def test_oserror_init(self):
833833
x = OSError()
834834
self.assertEqual(x.errno, None)
@@ -1025,6 +1025,39 @@ def test_windows_error(self):
10251025
# winerror code is mapped to Python error code
10261026
self.assertEqual(OSError('foo', 'bar', 'baz', 10).errno, 7)
10271027

1028+
def test_os_error_mapping(self):
1029+
import errno
1030+
import itertools
1031+
1032+
error_map = {
1033+
errno.EPERM : PermissionError,
1034+
errno.ENOENT : FileNotFoundError,
1035+
errno.ESRCH : ProcessLookupError,
1036+
errno.EINTR : InterruptedError,
1037+
errno.ECHILD : ChildProcessError,
1038+
errno.EAGAIN : BlockingIOError,
1039+
errno.EACCES : PermissionError,
1040+
errno.EEXIST : FileExistsError,
1041+
errno.ENOTDIR : NotADirectoryError,
1042+
errno.EISDIR : IsADirectoryError,
1043+
errno.EPIPE : BrokenPipeError,
1044+
errno.EWOULDBLOCK : BlockingIOError,
1045+
errno.ECONNABORTED : ConnectionAbortedError,
1046+
errno.ECONNRESET : ConnectionResetError,
1047+
errno.ESHUTDOWN : BrokenPipeError,
1048+
errno.ETIMEDOUT : TimeoutError,
1049+
errno.ECONNREFUSED : ConnectionRefusedError,
1050+
errno.EALREADY : BlockingIOError,
1051+
errno.EINPROGRESS : BlockingIOError,
1052+
}
1053+
1054+
for errno, exc in error_map.items():
1055+
self.assertIsInstance(OSError(errno, ''), exc)
1056+
1057+
for errno in itertools.chain(range(200), range(10000, 10200)):
1058+
if errno not in error_map:
1059+
self.assertIsInstance(OSError(errno, ''), OSError)
1060+
10281061
def test_derived_keyword_args(self):
10291062
class ED(Exception):
10301063
def __init__(self, args=''):

0 commit comments

Comments
 (0)