def test_catch_nested_group(): value_runtime_errors = [] zero_division_errors = [] with catch( { (ValueError, RuntimeError): value_runtime_errors.append, ZeroDivisionError: zero_division_errors.append, } ): nested_group = ExceptionGroup( "nested", [RuntimeError("bar"), ZeroDivisionError()] ) raise ExceptionGroup("booboo", [ValueError("foo"), nested_group]) assert len(value_runtime_errors) == 1 exceptions = value_runtime_errors[0].exceptions assert isinstance(exceptions[0], ValueError) assert isinstance(exceptions[1], ExceptionGroup) assert isinstance(exceptions[1].exceptions[0], RuntimeError) assert len(zero_division_errors) == 1 assert isinstance(zero_division_errors[0], ExceptionGroup) assert isinstance(zero_division_errors[0].exceptions[0], ExceptionGroup) assert isinstance( zero_division_errors[0].exceptions[0].exceptions[0], ZeroDivisionError )
def test_bad_EG_construction__bad_excs_sequence(self): MSG = r"second argument \(exceptions\) must be a sequence" with self.assertRaisesRegex(TypeError, MSG): ExceptionGroup("errors not sequence", {ValueError(42)}) with self.assertRaisesRegex(TypeError, MSG): ExceptionGroup("eg", None) MSG = r"second argument \(exceptions\) must be a non-empty sequence" with self.assertRaisesRegex(ValueError, MSG): ExceptionGroup("eg", [])
def test_catch_nested_group(): value_runtime_errors = [] zero_division_errors = [] try: nested_group = ExceptionGroup( "nested", [RuntimeError("bar"), ZeroDivisionError()] ) raise ExceptionGroup("booboo", [ValueError("foo"), nested_group]) except* (ValueError, RuntimeError) as exc: value_runtime_errors.append(exc)
def test_fields_are_readonly(self): eg = ExceptionGroup("eg", [TypeError(1), OSError(2)]) self.assertEqual(type(eg.exceptions), tuple) eg.message with self.assertRaises(AttributeError): eg.message = "new msg" eg.exceptions with self.assertRaises(AttributeError): eg.exceptions = [OSError("xyz")]
def test_bad_EG_construction__too_few_args(self): if sys.version_info >= (3, 11): MSG = ( r"BaseExceptionGroup.__new__\(\) takes exactly 2 arguments \(1 given\)" ) else: MSG = (r"__new__\(\) missing 1 required positional argument: " r"'_ExceptionGroup__exceptions'") with self.assertRaisesRegex(TypeError, MSG): ExceptionGroup("no errors") with self.assertRaisesRegex(TypeError, MSG): ExceptionGroup([ValueError("no msg")])
def test_exception_group_when_members_are_not_exceptions(): with pytest.raises(TypeError): ExceptionGroup( "error", [RuntimeError("RuntimeError"), "error2"], ["RuntimeError", "error2"], )
def test_split_and_check_attributes_same(): try: raise_error(RuntimeError("RuntimeError")) except Exception as e: run_error = e try: raise_error(ValueError("ValueError")) except Exception as e: val_error = e group = ExceptionGroup("ErrorGroup", [run_error, val_error], ["RuntimeError", "ValueError"]) # go and check __traceback__, __cause__ attributes try: raise_error_from_another(group, RuntimeError("Cause")) except BaseException as e: new_group = e matched, unmatched = split(RuntimeError, group) assert matched.__traceback__ is new_group.__traceback__ assert matched.__cause__ is new_group.__cause__ assert matched.__context__ is new_group.__context__ assert matched.__suppress_context__ is new_group.__suppress_context__ assert unmatched.__traceback__ is new_group.__traceback__ assert unmatched.__cause__ is new_group.__cause__ assert unmatched.__context__ is new_group.__context__ assert unmatched.__suppress_context__ is new_group.__suppress_context__
def create_simple_eg(): excs = [] try: try: raise MemoryError("context and cause for ValueError(1)") except MemoryError as e: raise ValueError(1) from e except ValueError as e: excs.append(e) try: try: raise OSError("context for TypeError") except OSError: raise TypeError(int) except TypeError as e: excs.append(e) try: try: raise ImportError("context for ValueError(2)") except ImportError: raise ValueError(2) except ValueError as e: excs.append(e) try: raise ExceptionGroup("simple eg", excs) except ExceptionGroup as e: return e
def level1(i): excs = [] for f, arg in [(raiseVE, i), (raiseTE, int), (raiseVE, i + 1)]: try: f(arg) except Exception as e: excs.append(e) raise ExceptionGroup("msg1", excs)
def test_repr(): group = BaseExceptionGroup("foo", [ValueError(1), KeyboardInterrupt()]) assert repr(group) == ( "BaseExceptionGroup('foo', [ValueError(1), KeyboardInterrupt()])" ) group = ExceptionGroup("foo", [ValueError(1), RuntimeError("bar")]) assert repr(group) == "ExceptionGroup('foo', [ValueError(1), RuntimeError('bar')])"
def level3(i): excs = [] for f, arg in [(level2, i + 1), (raiseVE, i + 2)]: try: f(arg) except Exception as e: excs.append(e) raise ExceptionGroup("msg3", excs)
def test_catch_nested_group(): exceptions1 = [] exceptions2 = [] with catch({ (ValueError, RuntimeError): exceptions1.append, ZeroDivisionError: exceptions2.append, }): nested_group = ExceptionGroup( "nested", [RuntimeError("bar"), ZeroDivisionError()]) raise ExceptionGroup("booboo", [ValueError("foo"), nested_group]) assert len(exceptions1) == 2 assert isinstance(exceptions1[0], ValueError) assert isinstance(exceptions1[1], RuntimeError) assert len(exceptions2) == 1 assert isinstance(exceptions2[0], ZeroDivisionError)
def test_catch_no_match(): try: with catch({(ValueError, RuntimeError): (lambda e: None)}): group = ExceptionGroup("booboo", [ZeroDivisionError()]) raise group except ExceptionGroup as exc: assert exc is not group else: pytest.fail("Did not raise an ExceptionGroup")
def test_bad_EG_construction__too_many_args(self): if sys.version_info >= (3, 11): MSG = ( r"BaseExceptionGroup.__new__\(\) takes exactly 2 arguments \(3 given\)" ) else: MSG = r"__new__\(\) takes 3 positional arguments but 4 were given" with self.assertRaisesRegex(TypeError, MSG): ExceptionGroup("eg", [ValueError("too")], [TypeError("many")])
def test_split_when_all_exception_unmatched(): group = ExceptionGroup( "Many Errors", [RuntimeError("Runtime Error1"), RuntimeError("Runtime Error2")], ["Runtime Error1", "Runtime Error2"], ) matched, unmatched = split(ValueError, group) assert matched is None assert unmatched is group
def test_split_with_predicate(): def _match(err): return str(err) != "skip" error1 = RuntimeError("skip") error2 = RuntimeError("Runtime Error") group = ExceptionGroup("Many Errors", [error1, error2], ["skip", "Runtime Error"]) matched, unmatched = split(RuntimeError, group, match=_match) assert matched.exceptions == [error2] assert unmatched.exceptions == [error1]
def test_catch_no_match(): try: try: group = ExceptionGroup("booboo", [ZeroDivisionError()]) raise group except* (ValueError, RuntimeError): pass except ExceptionGroup as exc: assert isinstance(exc.exceptions[0], ZeroDivisionError) assert exc is not group else: pytest.fail("Did not raise an ExceptionGroup")
def test_exception_group_str(): memberA = ValueError("memberA") memberB = ValueError("memberB") group = ExceptionGroup( "many error.", [memberA, memberB], [str(memberA), str(memberB)] ) assert "memberA" in str(group) assert "memberB" in str(group) assert "ExceptionGroup: " in repr(group) assert "memberA" in repr(group) assert "memberB" in repr(group)
def test_catch_handler_raises(): try: try: raise ExceptionGroup("booboo", [ValueError("bar")]) except* ValueError: raise RuntimeError("new") except ExceptionGroup as exc: assert exc.message == "" assert len(exc.exceptions) == 1 assert isinstance(exc.exceptions[0], RuntimeError) else: pytest.fail("Did not raise an ExceptionGroup")
def create_nested_eg(): excs = [] try: try: raise TypeError(bytes) except TypeError as e: raise ExceptionGroup("nested", [e]) except ExceptionGroup as e: excs.append(e) try: try: raise MemoryError("out of memory") except MemoryError as e: raise ValueError(1) from e except ValueError as e: excs.append(e) try: raise ExceptionGroup("root", excs) except ExceptionGroup as eg: return eg
def test_formatting(capsys): exceptions = [] try: raise ValueError("foo") except ValueError as exc: exceptions.append(exc) try: raise RuntimeError("bar") except RuntimeError as exc: exc.__note__ = "Note from bar handler" exceptions.append(exc) try: raise ExceptionGroup("test message", exceptions) except ExceptionGroup as exc: exc.__note__ = "Displays notes attached to the group too" sys.excepthook(type(exc), exc, exc.__traceback__) lineno = test_formatting.__code__.co_firstlineno if sys.version_info >= (3, 11): module_prefix = "" underline1 = "\n | " + "^" * 48 underline2 = "\n | " + "^" * 23 underline3 = "\n | " + "^" * 25 else: module_prefix = "exceptiongroup." underline1 = underline2 = underline3 = "" output = capsys.readouterr().err assert output == ( f"""\ + Exception Group Traceback (most recent call last): | File "{__file__}", line {lineno + 14}, in test_formatting | raise ExceptionGroup("test message", exceptions){underline1} | {module_prefix}ExceptionGroup: test message | Displays notes attached to the group too +-+---------------- 1 ---------------- | Traceback (most recent call last): | File "{__file__}", line {lineno + 3}, in test_formatting | raise ValueError("foo"){underline2} | ValueError: foo +---------------- 2 ---------------- | Traceback (most recent call last): | File "{__file__}", line {lineno + 8}, in test_formatting | raise RuntimeError("bar"){underline3} | RuntimeError: bar | Note from bar handler +------------------------------------ """ )
def test_exception_group_init(): memberA = ValueError("A") memberB = RuntimeError("B") group = ExceptionGroup( "many error.", [memberA, memberB], [str(memberA), str(memberB)] ) assert group.exceptions == [memberA, memberB] assert group.message == "many error." assert group.sources == [str(memberA), str(memberB)] assert group.args == ( "many error.", [memberA, memberB], [str(memberA), str(memberB)], )
def test_split_when_contains_matched_and_unmatched(): error1 = RuntimeError("Runtime Error1") error2 = ValueError("Value Error2") group = ExceptionGroup("Many Errors", [error1, error2], ["Runtime Error1", "Value Error2"]) matched, unmatched = split(RuntimeError, group) assert isinstance(matched, ExceptionGroup) assert isinstance(unmatched, ExceptionGroup) assert matched.exceptions == [error1] assert matched.message == "Many Errors" assert matched.sources == ["Runtime Error1"] assert unmatched.exceptions == [error2] assert unmatched.message == "Many Errors" assert unmatched.sources == ["Value Error2"]
def test_catch_group(): value_runtime_errors = [] zero_division_errors = [] try: raise ExceptionGroup( "booboo", [ ValueError("foo"), ValueError("bar"), RuntimeError("bar"), ZeroDivisionError(), ], ) except* (ValueError, RuntimeError) as exc: value_runtime_errors.append(exc)
def test_catch_full_match(): with catch({(ValueError, RuntimeError): (lambda e: None)}): raise ExceptionGroup("booboo", [ValueError()])
def make_deep_eg(self): e = TypeError(1) for _ in range(2000): e = ExceptionGroup("eg", [e]) return e
def test_EG_wraps_BaseException__raises_TypeError(self): MSG = "Cannot nest BaseExceptions in an ExceptionGroup" with self.assertRaisesRegex(TypeError, MSG): ExceptionGroup("eg", [ValueError(1), KeyboardInterrupt(2)])
def test_EG_wraps_Exceptions__creates_EG(self): excs = [ValueError(1), TypeError(2)] self.assertIs(type(ExceptionGroup("eg", excs)), ExceptionGroup)
def test_bad_EG_construction__nested_non_exceptions(self): MSG = r"Item [0-9]+ of second argument \(exceptions\) is not an exception" with self.assertRaisesRegex(ValueError, MSG): ExceptionGroup("expect instance, not type", [OSError]) with self.assertRaisesRegex(ValueError, MSG): ExceptionGroup("bad error", ["not an exception"])
def test_bad_EG_construction__bad_message(self): MSG = "argument 1 must be str, not " with self.assertRaisesRegex(TypeError, MSG): ExceptionGroup(ValueError(12), SyntaxError("bad syntax")) with self.assertRaisesRegex(TypeError, MSG): ExceptionGroup(None, [ValueError(12)])