diff --git a/awslambdaric/bootstrap.py b/awslambdaric/bootstrap.py index e737b7b..f326ff4 100644 --- a/awslambdaric/bootstrap.py +++ b/awslambdaric/bootstrap.py @@ -208,7 +208,8 @@ def handle_event_request( xray_fault = make_xray_fault(etype.__name__, str(value), os.getcwd(), tb_tuples) error_result = make_error( - str(value), etype.__name__, traceback.format_list(tb_tuples), invoke_id + '; '.join(str(e) for e in flatten_exceptiongroup(value)), + etype.__name__, traceback.format_list(tb_tuples), invoke_id ) if error_result is not None: @@ -292,6 +293,14 @@ def make_xray_fault(ex_type, ex_msg, working_dir, tb_tuples): return xray_fault +def flatten_exceptiongroup(e): + exceptions = [e] + if isinstance(e, BaseExceptionGroup): + for sube in e.exceptions: + exceptions.extend(flatten_exceptiongroup(sube)) + return tuple(exceptions) + + def extract_traceback(tb): return [ (frame.filename, frame.lineno, frame.name, frame.line) diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index fd56d9f..50748ba 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -355,6 +355,46 @@ def __init__(self, message): self.assertEqual(len(xray_fault["paths"]), 1) self.assertTrue(xray_fault["paths"][0].endswith(os.path.relpath(__file__))) + def test_handle_event_request_exception_group(self): + def raise_exception_handler(json_input, lambda_context): + try: + raise RuntimeError("Oops") + except Exception as e: + raise ExceptionGroup("We have nested exceptions", (e,)) + + file_path = os.path.relpath(__file__) + expected_response = { + "errorType": "ExceptionGroup", + "errorMessage": "We have nested exceptions (1 sub-exception); Oops", + "requestId": "invoke_id", + "stackTrace": [ + f' File "{file_path}", line _, in raise_exception_handler\n' + ' raise ExceptionGroup("We have nested exceptions", (e,))\n' + ], + } + bootstrap.handle_event_request( + self.lambda_runtime, + raise_exception_handler, + "invoke_id", + self.event_body, + "application/json", + {}, + {}, + "invoked_function_arn", + 0, + bootstrap.StandardLogSink(), + ) + args, _ = self.lambda_runtime.post_invocation_error.call_args + error_response = json.loads(args[1]) + self.assertEqual(args[0], "invoke_id") + error_response["stackTrace"] = [ + re.sub(fr'".*/{file_path}"', f'"{file_path}"', + re.sub(r" line \d+,", " line _,", line) + ) + for line in error_response["stackTrace"] + ] + self.assertEqual(error_response.items(), expected_response.items()) + def test_handle_event_request_no_module(self): def unable_to_import_module(json_input, lambda_context): import invalid_module # noqa: F401