From ad3ade8e8456f84124b78eb67e5c711bf19b6ad3 Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Thu, 23 Jul 2020 17:11:30 -0300 Subject: [PATCH] Show user traceback for errors raised in the debug console. Fixes #328 --- .../pydevd/_pydevd_bundle/pydevd_comm.py | 29 +++++++++++- .../pydevd/tests_python/test_debugger_json.py | 45 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index c4981a8b4..2768de978 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -85,6 +85,7 @@ from _pydevd_bundle.pydevd_constants import ForkSafeLock, NULL from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id, resume_threads +from _pydevd_bundle.pydevd_dont_trace_files import PYDEV_FILE try: from urllib import quote_plus, unquote_plus # @UnresolvedImport except: @@ -953,8 +954,32 @@ def __create_frame(): if try_exec: try: pydevd_vars.evaluate_expression(py_db, frame, expression, is_exec=True) - except (Exception, KeyboardInterrupt) as ex: - err = ''.join(traceback.format_exception_only(type(ex), ex)) + except (Exception, KeyboardInterrupt): + try: + exc, exc_type, initial_tb = sys.exc_info() + tb = initial_tb + + # Show the traceback without pydevd frames. + temp_tb = tb + while temp_tb: + if py_db.get_file_type(temp_tb.tb_frame) == PYDEV_FILE: + tb = temp_tb.tb_next + temp_tb = temp_tb.tb_next + + if tb is None: + tb = initial_tb + err = ''.join(traceback.format_exception(exc, exc_type, tb)) + + # Make sure we don't keep references to them. + exc = None + exc_type = None + tb = None + temp_tb = None + initial_tb = None + except: + err = '' + pydev_log.exception(err) + # Currently there is an issue in VSC where returning success=false for an # eval request, in repl context, VSC does not show the error response in # the debug console. So return the error message in result as well. 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 95d2af648..5d5d62128 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py @@ -2600,6 +2600,51 @@ def test_evaluate_failures(case_setup): writer.finished_ok = True +def test_evaluate_exception_trace(case_setup, pyfile): + + @pyfile + def exception_trace_file(): + + class A(object): + + def __init__(self, a): + pass + + def method(): + A() + + def method2(): + method() + + def method3(): + method2() + + print('TEST SUCEEDED') # Break here + + with case_setup.test_file(exception_trace_file) as writer: + json_facade = JsonFacade(writer) + json_facade.write_launch(justMyCode=False) + + json_facade.write_set_breakpoints(writer.get_line_index_with_content('Break here')) + json_facade.write_make_initial_run() + + json_hit = json_facade.wait_for_thread_stopped() + + exec_response = json_facade.evaluate('method3()', json_hit.frame_id, 'repl', success=False) + assert 'pydevd' not in exec_response.message # i.e.: don't show pydevd in the trace + assert 'method3' in exec_response.message + assert 'method2' in exec_response.message + + exec_response = json_facade.evaluate('method2()', json_hit.frame_id, 'repl', success=False) + assert 'pydevd' not in exec_response.message + assert 'method3' not in exec_response.message + assert 'method2' in exec_response.message + + json_facade.write_continue() + + writer.finished_ok = True + + @pytest.mark.parametrize('max_frames', ['default', 'all', 10]) # -1 = default, 0 = all, 10 = 10 frames def test_exception_details(case_setup, max_frames): with case_setup.test_file('_debugger_case_large_exception_stack.py') as writer: