diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index cf85d5da6..d63899f7b 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -1467,6 +1467,19 @@ def build_exception_info_response(dbg, thread_id, request_seq, set_additional_th source_path = frames[0][0] stack_str = ''.join(traceback.format_list(frames[-max_frames:])) + + try: + stype = frames_list.exc_type.__qualname__ + smod = frames_list.exc_type.__module__ + if smod not in ("__main__", "builtins"): + if not isinstance(smod, str): + smod = "" + stype = smod + '.' + stype + except Exception: + stype = '' + pydev_log.exception('Error getting exception type.') + + stack_str += '%s: %s\n' % (stype, frames_list.exc_desc) stack_str += frames_list.exc_context_msg stack_str_lst.append(stack_str) diff --git a/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_raise_with_cause_msg.py b/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_raise_with_cause_msg.py new file mode 100644 index 000000000..b9709f08a --- /dev/null +++ b/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_raise_with_cause_msg.py @@ -0,0 +1,12 @@ +def method2(): + {}['foo'] + + +def method(): + try: + method2() + except Exception as e: + raise Exception('TEST SUCEEDED') from e + + +method() diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py index be8ff64f5..60c9846b7 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py @@ -719,7 +719,6 @@ def test_case_handled_exception_no_break_on_generator(case_setup): writer.finished_ok = True -@pytest.mark.skipif(not IS_PY36_OR_GREATER, reason='Requires Python 3.') def test_case_throw_exc_reason(case_setup): def check_test_suceeded_msg(self, stdout, stderr): @@ -771,6 +770,53 @@ def additional_output_checks(writer, stdout, stderr): writer.finished_ok = True +def test_case_throw_exc_reason_shown(case_setup): + + def check_test_suceeded_msg(self, stdout, stderr): + return 'TEST SUCEEDED' in ''.join(stderr) + + def additional_output_checks(writer, stdout, stderr): + assert "raise Exception('TEST SUCEEDED') from e" in stderr + assert "{}['foo']" in stderr + assert "KeyError: 'foo'" in stderr + + with case_setup.test_file( + '_debugger_case_raise_with_cause_msg.py', + EXPECTED_RETURNCODE=1, + check_test_suceeded_msg=check_test_suceeded_msg, + additional_output_checks=additional_output_checks + ) as writer: + json_facade = JsonFacade(writer) + + json_facade.write_launch(justMyCode=False) + json_facade.write_set_exception_breakpoints(['uncaught']) + json_facade.write_make_initial_run() + + json_hit = json_facade.wait_for_thread_stopped( + reason='exception', line=writer.get_line_index_with_content("raise Exception('TEST SUCEEDED') from e")) + + exc_info_request = json_facade.write_request( + pydevd_schema.ExceptionInfoRequest(pydevd_schema.ExceptionInfoArguments(json_hit.thread_id))) + exc_info_response = json_facade.wait_for_response(exc_info_request) + + stack_frames = json_hit.stack_trace_response.body.stackFrames + # Note that the additional context doesn't really appear in the stack + # frames, only in the details. + assert [x['name'] for x in stack_frames] == ['method', ''] + + body = exc_info_response.body + assert body.exceptionId == 'Exception' + assert body.description == 'TEST SUCEEDED' + assert normcase(body.details.kwargs['source']) == normcase(writer.TEST_FILE) + + # Check that we have the exception cause in the stack trace. + assert "KeyError: 'foo'" in body.details.stackTrace + + json_facade.write_continue() + + writer.finished_ok = True + + def test_case_handled_exception_breaks(case_setup): with case_setup.test_file('_debugger_case_exceptions.py') as writer: json_facade = JsonFacade(writer)