Skip to content

Commit 072e1ff

Browse files
committed
Add tests for native exception subclasses
1 parent b8d1253 commit 072e1ff

File tree

2 files changed

+175
-23
lines changed

2 files changed

+175
-23
lines changed

graalpython/com.oracle.graal.python.test/src/tests/cpyext/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ def CPyExtType(name, code, **kwargs):
591591
{tp_getattro},
592592
{tp_setattro},
593593
{tp_as_buffer},
594-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
594+
{tp_flags},
595595
"",
596596
{tp_traverse}, /* tp_traverse */
597597
{tp_clear}, /* tp_clear */
@@ -648,6 +648,7 @@ def CPyExtType(name, code, **kwargs):
648648
kwargs.setdefault("tp_new", "PyType_GenericNew")
649649
kwargs.setdefault("tp_alloc", "PyType_GenericAlloc")
650650
kwargs.setdefault("tp_free", "PyObject_Del")
651+
kwargs.setdefault("tp_flags", "Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE")
651652
kwargs.setdefault("cmembers", "")
652653
kwargs.setdefault("includes", "")
653654
kwargs.setdefault("struct_base", "PyObject_HEAD")

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_err.py

Lines changed: 173 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@
3939

4040
import sys
4141
import warnings
42-
from . import CPyExtTestCase, CPyExtFunction, CPyExtFunctionOutVars, CPyExtFunctionVoid, unhandled_error_compare, GRAALPYTHON
42+
43+
from . import CPyExtTestCase, CPyExtFunction, CPyExtFunctionVoid, unhandled_error_compare, \
44+
CPyExtType
45+
4346
__dir__ = __file__.rpartition("/")[0]
4447

4548

@@ -73,25 +76,27 @@ def _reference_restore(args):
7376

7477
def compare_restore_result(x, y):
7578
return (
76-
isinstance(x, BaseException) and
77-
type(x) == type(y) and
78-
# Compare str because different exceptions are not equal
79-
str(x.args) == str(y.args) and
80-
(x.__traceback__ is example_traceback) == (y.__traceback__ is example_traceback)
79+
isinstance(x, BaseException) and
80+
type(x) == type(y) and
81+
# Compare str because different exceptions are not equal
82+
str(x.args) == str(y.args) and
83+
(x.__traceback__ is example_traceback) == (y.__traceback__ is example_traceback)
8184
)
8285

8386

8487
def _new_ex_result_check(x, y):
8588
name = y[0]
8689
base = y[1]
8790
return name in str(x) and issubclass(x, base)
88-
91+
92+
8993
def _new_ex_with_doc_result_check(x, y):
9094
name = y[0]
9195
doc = y[1]
9296
base = y[2]
9397
return name in str(x) and issubclass(x, base) and x.__doc__ == doc
9498

99+
95100
def _reference_setnone(args):
96101
raise args[0]()
97102

@@ -145,8 +150,10 @@ def _reference_fetch_tb_f_back(args):
145150
def _raise_exception():
146151
def inner():
147152
raise OSError
153+
148154
def reraise(e):
149155
raise e
156+
150157
try:
151158
inner()
152159
except Exception as e:
@@ -193,6 +200,16 @@ def raise_erorr():
193200

194201
assert example_traceback
195202

203+
ExceptionSubclass = CPyExtType(
204+
"ExceptionSubclass",
205+
'',
206+
struct_base='PyBaseExceptionObject base',
207+
tp_new='0',
208+
tp_alloc='0',
209+
tp_free='0',
210+
ready_code='ExceptionSubclassType.tp_base = (PyTypeObject*)PyExc_Exception;'
211+
)
212+
196213

197214
class TestPyErr(CPyExtTestCase):
198215

@@ -206,14 +223,15 @@ def compile_module(self, name):
206223
(ValueError, "hello"),
207224
(TypeError, "world"),
208225
(KeyError, "key"),
226+
(ExceptionSubclass, "hello")
209227
),
210228
resultspec="O",
211229
argspec='Os',
212230
arguments=["PyObject* v", "char* msg"],
213231
resultval="NULL",
214232
cmpfunc=unhandled_error_compare
215233
)
216-
234+
217235
test_PyErr_NewException = CPyExtFunction(
218236
lambda args: args,
219237
lambda: (
@@ -224,7 +242,7 @@ def compile_module(self, name):
224242
arguments=["char* name", "PyObject* base", "PyObject* dict"],
225243
cmpfunc=_new_ex_result_check
226244
)
227-
245+
228246
test_PyErr_NewExceptionWithDoc = CPyExtFunction(
229247
lambda args: args,
230248
lambda: (
@@ -235,7 +253,7 @@ def compile_module(self, name):
235253
arguments=["char* name", "char* doc", "PyObject* base", "PyObject* dict"],
236254
cmpfunc=_new_ex_with_doc_result_check
237255
)
238-
256+
239257
test_PyErr_SetObject = CPyExtFunctionVoid(
240258
_reference_setobject,
241259
lambda: (
@@ -246,6 +264,9 @@ def compile_module(self, name):
246264
(KeyError, "key"),
247265
(RuntimeError, ValueError()),
248266
(OSError, (2, "error")),
267+
(ExceptionSubclass, None),
268+
(ExceptionSubclass, "hello"),
269+
(ExceptionSubclass, (1, 2)),
249270
),
250271
resultspec="O",
251272
argspec='OO',
@@ -260,6 +281,7 @@ def compile_module(self, name):
260281
(ValueError,),
261282
(TypeError,),
262283
(KeyError,),
284+
(ExceptionSubclass,),
263285
),
264286
resultspec="O",
265287
argspec='O',
@@ -275,6 +297,7 @@ def compile_module(self, name):
275297
(TypeError, "world %S %S", "", ""),
276298
(KeyError, "key %S %S", "", ""),
277299
(KeyError, "unknown key: %S %S", "some_key", ""),
300+
(ExceptionSubclass, "hello %S %S", "beautiful", "world"),
278301
),
279302
resultspec="O",
280303
argspec='OsOO',
@@ -319,15 +342,17 @@ def compile_module(self, name):
319342
test_PyErr_GivenExceptionMatches = CPyExtFunction(
320343
_reference_givenexceptionmatches,
321344
lambda: (
322-
(ValueError(), ValueError),
323-
(ValueError(), BaseException),
324-
(ValueError(), KeyError),
325-
(ValueError(), (KeyError, SystemError, OverflowError)),
326-
(ValueError(), Dummy),
327-
(ValueError(), Dummy()),
328-
(Dummy(), Dummy()),
329-
(Dummy(), Dummy),
330-
(Dummy(), KeyError),
345+
(ValueError, ValueError),
346+
(ValueError, BaseException),
347+
(ValueError, KeyError),
348+
(ValueError, (KeyError, SystemError, OverflowError)),
349+
(ValueError, (KeyError, SystemError, ValueError)),
350+
(ValueError, Dummy),
351+
(ValueError, Dummy()),
352+
(ValueError, ExceptionSubclass),
353+
(ExceptionSubclass, ExceptionSubclass),
354+
(ExceptionSubclass, Exception),
355+
(ExceptionSubclass, ValueError),
331356
),
332357
resultspec="i",
333358
argspec='OO',
@@ -394,8 +419,14 @@ def compile_module(self, name):
394419
(ValueError, BaseException),
395420
(ValueError, KeyError),
396421
(ValueError, (KeyError, SystemError, OverflowError)),
422+
(ValueError, (KeyError, SystemError, ValueError)),
397423
(ValueError, Dummy),
398424
(ValueError, Dummy()),
425+
(ValueError, ExceptionSubclass),
426+
(ExceptionSubclass, ExceptionSubclass),
427+
(ExceptionSubclass, Exception),
428+
(ExceptionSubclass, ValueError),
429+
399430
),
400431
code="""int wrap_PyErr_ExceptionMatches(PyObject* err, PyObject* exc) {
401432
int res;
@@ -431,7 +462,8 @@ def compile_module(self, name):
431462
),
432463
resultspec="O",
433464
argspec='OOOiOO',
434-
arguments=["PyObject* category", "PyObject* text", "PyObject* filename_str", "int lineno", "PyObject* module_str", "PyObject* registry"],
465+
arguments=["PyObject* category", "PyObject* text", "PyObject* filename_str", "int lineno",
466+
"PyObject* module_str", "PyObject* registry"],
435467
stderr_validator=lambda args, stderr: "UserWarning: custom warning" in stderr,
436468
cmpfunc=unhandled_error_compare
437469
)
@@ -443,7 +475,8 @@ def compile_module(self, name):
443475
),
444476
resultspec="O",
445477
argspec='OssisO',
446-
arguments=["PyObject* category", "const char* text", "const char* filename_str", "int lineno", "const char* module_str", "PyObject* registry"],
478+
arguments=["PyObject* category", "const char* text", "const char* filename_str", "int lineno",
479+
"const char* module_str", "PyObject* registry"],
447480
stderr_validator=lambda args, stderr: "UserWarning: custom warning" in stderr,
448481
cmpfunc=unhandled_error_compare
449482
)
@@ -496,7 +529,8 @@ def compile_module(self, name):
496529
argspec='O',
497530
arguments=["PyObject* obj"],
498531
callfunction="wrap_PyErr_WriteUnraisableMsg",
499-
stderr_validator=lambda args, stderr: "RuntimeError: unraisable_exception" in stderr and "Exception ignored in my function:" in stderr,
532+
stderr_validator=lambda args,
533+
stderr: "RuntimeError: unraisable_exception" in stderr and "Exception ignored in my function:" in stderr,
500534
cmpfunc=unhandled_error_compare
501535
)
502536

@@ -511,6 +545,7 @@ def compile_module(self, name):
511545
(RuntimeError, ValueError(), None),
512546
(OSError, (2, "error"), None),
513547
(NameError, None, example_traceback),
548+
(ExceptionSubclass, "error", None),
514549
),
515550
# Note on CPython all the exception creation happens not in PyErr_Restore, but when leaving the function and
516551
# normalizing the exception in the caller. So this really test both of these mechanisms together.
@@ -618,3 +653,119 @@ def compile_module(self, name):
618653
# callfunction="wrap_PyErr_Fetch_tb_f_back",
619654
# cmpfunc=compare_frame_f_back_chain,
620655
# )
656+
657+
658+
def raise_native_exception():
659+
raise ExceptionSubclass(1)
660+
661+
662+
class TestNativeExceptionSubclass:
663+
def test_raise_type(self):
664+
try:
665+
raise ExceptionSubclass
666+
except ExceptionSubclass as e:
667+
assert e.args == ()
668+
else:
669+
assert False
670+
671+
def test_raise_instance(self):
672+
try:
673+
raise ExceptionSubclass(1)
674+
except ExceptionSubclass as e:
675+
assert e.args == (1,)
676+
else:
677+
assert False
678+
679+
def test_traceback(self):
680+
try:
681+
try:
682+
raise_native_exception()
683+
finally:
684+
assert True # no-op, just to test implicit reraising
685+
except ExceptionSubclass as e:
686+
tb = e.__traceback__
687+
else:
688+
assert False
689+
assert tb
690+
assert tb.tb_frame.f_code is TestNativeExceptionSubclass.test_traceback.__code__
691+
assert tb.tb_next
692+
assert tb.tb_next.tb_frame.f_code is raise_native_exception.__code__
693+
assert tb.tb_next.tb_lineno == raise_native_exception.__code__.co_firstlineno + 1
694+
e2 = ExceptionSubclass()
695+
assert e2.__traceback__ is None
696+
e2.__traceback__ = tb
697+
assert e2.__traceback__ is tb
698+
e2 = ExceptionSubclass()
699+
e2 = e2.with_traceback(tb)
700+
assert e2.__traceback__ is tb
701+
702+
def test_chaining(self):
703+
inner_e = ExceptionSubclass()
704+
outer_e = ExceptionSubclass()
705+
try:
706+
try:
707+
raise inner_e
708+
except Exception:
709+
raise outer_e
710+
except Exception:
711+
pass
712+
assert outer_e.__context__ is inner_e
713+
assert outer_e.__suppress_context__ is False
714+
715+
def test_raise_from(self):
716+
inner_e = ExceptionSubclass()
717+
outer_e = ExceptionSubclass()
718+
try:
719+
raise outer_e from inner_e
720+
except Exception:
721+
pass
722+
assert outer_e.__cause__ is inner_e
723+
assert outer_e.__suppress_context__ is True
724+
725+
def test_cause(self):
726+
e = ExceptionSubclass()
727+
e2 = ExceptionSubclass()
728+
assert e.__suppress_context__ is False
729+
e.__cause__ = e2
730+
assert e.__cause__ is e2
731+
assert e.__suppress_context__ is True
732+
e.__suppress_context__ = False
733+
assert e.__suppress_context__ is False
734+
735+
def test_context(self):
736+
e = ExceptionSubclass()
737+
e2 = ExceptionSubclass()
738+
e.__context__ = e2
739+
assert e.__context__ is e2
740+
741+
def test_args(self):
742+
e = ExceptionSubclass(1, 2, 3)
743+
assert e.args == (1, 2, 3)
744+
e.args = ("foo",)
745+
assert e.args == ("foo",)
746+
747+
def test_dict(self):
748+
e = ExceptionSubclass()
749+
e.asdf = 1
750+
assert e.asdf == 1
751+
assert e.__dict__ == {'asdf': 1}
752+
e.__dict__ = {'foo': 'bar'}
753+
assert e.__dict__ == {'foo': 'bar'}
754+
755+
def test_reduce(self):
756+
e = ExceptionSubclass(1)
757+
e.asdf = 1
758+
assert e.__reduce__() == (ExceptionSubclass, (1,), {'asdf': 1})
759+
760+
def test_repr_str(self):
761+
assert repr(ExceptionSubclass()) == "ExceptionSubclass()"
762+
assert repr(ExceptionSubclass(1)) == "ExceptionSubclass(1)"
763+
assert repr(ExceptionSubclass(1, 2)) == "ExceptionSubclass(1, 2)"
764+
assert str(ExceptionSubclass()) == ""
765+
assert str(ExceptionSubclass(1)) == "1"
766+
assert str(ExceptionSubclass(1, 2)) == "(1, 2)"
767+
768+
def test_setstate(self):
769+
e = ExceptionSubclass()
770+
e.__setstate__({'foo': 'bar'})
771+
assert e.foo == 'bar'

0 commit comments

Comments
 (0)