-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathexception_test.py
191 lines (145 loc) · 6.28 KB
/
exception_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
"""
Tests exceptions thrown from functions wrapped by Synchronicity.
Currently, exceptions are thrown from Synchronicity like so:
try:
return self._func(*args, **kwargs)
except UserCodeException as uc_exc:
uc_exc.exc.__suppress_context__ = True
raise uc_exc.exc
When we raise an exception, the exception context is from Synchronicity, which
may confuse users. Therefore, we set __suppress_context__ to True to avoid
showing the user those error messages. This will preserve uc_exc.exc.__cause__,
but will cause uc_exc.exc.__context__ to be lost. Unfortunately, I don't know
how to avoid that.
These tests ensure that the __cause__ of an user exception is not lost, and
that either __suppress_context__ is True or __context__ is None so that users
are not exposed to confusing Synchronicity error messages.
See https://github.com/modal-labs/synchronicity/pull/165 for more details.
"""
import asyncio
import concurrent
import functools
import inspect
import pytest
import time
import typing
SLEEP_DELAY = 0.1
class CustomExceptionCause(Exception):
pass
class CustomException(Exception):
pass
async def f_raises():
await asyncio.sleep(0.1)
raise CustomException("something failed")
async def f_raises_with_cause():
await asyncio.sleep(0.1)
raise CustomException("something failed") from CustomExceptionCause("exception cause")
def test_function_raises_sync(synchronizer):
t0 = time.monotonic()
with pytest.raises(CustomException) as exc:
f_raises_s = synchronizer.create_blocking(f_raises)
f_raises_s()
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None
def test_function_raises_with_cause_sync(synchronizer):
t0 = time.monotonic()
with pytest.raises(CustomException) as exc:
f_raises_s = synchronizer.create_blocking(f_raises_with_cause)
f_raises_s()
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert isinstance(exc.value.__cause__, CustomExceptionCause)
def test_function_raises_sync_futures(synchronizer):
t0 = time.monotonic()
f_raises_s = synchronizer.create_blocking(f_raises)
fut = f_raises_s(_future=True)
assert isinstance(fut, concurrent.futures.Future)
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
fut.result()
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None
def test_function_raises_with_cause_sync_futures(synchronizer):
t0 = time.monotonic()
f_raises_s = synchronizer.create_blocking(f_raises_with_cause)
fut = f_raises_s(_future=True)
assert isinstance(fut, concurrent.futures.Future)
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
fut.result()
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert isinstance(exc.value.__cause__, CustomExceptionCause)
@pytest.mark.asyncio
async def test_function_raises_async(synchronizer):
t0 = time.monotonic()
f_raises_s = synchronizer.create_blocking(f_raises)
coro = f_raises_s.aio()
assert inspect.iscoroutine(coro)
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
await coro
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None
@pytest.mark.asyncio
async def test_function_raises_with_cause_async(synchronizer):
t0 = time.monotonic()
f_raises_s = synchronizer.create_blocking(f_raises_with_cause)
coro = f_raises_s.aio()
assert inspect.iscoroutine(coro)
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
await coro
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert isinstance(exc.value.__cause__, CustomExceptionCause)
async def f_raises_baseexc():
await asyncio.sleep(0.1)
raise KeyboardInterrupt
def test_function_raises_baseexc_sync(synchronizer):
t0 = time.monotonic()
with pytest.raises(BaseException) as exc:
f_raises_baseexc_s = synchronizer.create_blocking(f_raises_baseexc)
f_raises_baseexc_s()
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None
def f_raises_syncwrap() -> typing.Coroutine[typing.Any, typing.Any, None]:
return f_raises() # returns a coro
@pytest.mark.asyncio
async def test_function_raises_async_syncwrap(synchronizer):
t0 = time.monotonic()
f_raises_syncwrap_s = synchronizer.create_blocking(f_raises_syncwrap)
coro = f_raises_syncwrap_s.aio()
assert inspect.iscoroutine(coro)
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
await coro
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None
def f_raises_with_cause_syncwrap() -> typing.Coroutine[typing.Any, typing.Any, None]:
return f_raises_with_cause() # returns a coro
@pytest.mark.asyncio
async def test_function_raises_with_cause_async_syncwrap(synchronizer):
t0 = time.monotonic()
f_raises_syncwrap_s = synchronizer.create_blocking(f_raises_with_cause_syncwrap)
coro = f_raises_syncwrap_s.aio()
assert inspect.iscoroutine(coro)
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
await coro
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert isinstance(exc.value.__cause__, CustomExceptionCause)
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
f_raises_wrapped = decorator(f_raises)
@pytest.mark.asyncio
async def test_wrapped_function_raises_async(synchronizer):
t0 = time.monotonic()
f_raises_s = synchronizer.create_blocking(f_raises_wrapped)
coro = f_raises_s.aio()
assert inspect.iscoroutine(coro)
assert time.monotonic() - t0 < SLEEP_DELAY
with pytest.raises(CustomException) as exc:
await coro
assert SLEEP_DELAY < time.monotonic() - t0 < 2 * SLEEP_DELAY
assert exc.value.__suppress_context__ or exc.value.__context__ is None