mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
* Add json support for exception info request * Add tests for json exception info request * Address comments and minor tweaks * Rename and fix tests * Pass response to NetCommand
This commit is contained in:
parent
3538b2c234
commit
957b01002d
5 changed files with 158 additions and 68 deletions
|
|
@ -8,7 +8,7 @@ from _pydevd_bundle.pydevd_comm import (InternalGetThreadStack, internal_get_com
|
|||
InternalGetVariable, InternalGetArray, InternalLoadFullValue,
|
||||
internal_get_description, internal_get_frame, internal_evaluate_expression, InternalConsoleExec,
|
||||
internal_get_variable_json, internal_change_variable, internal_change_variable_json,
|
||||
internal_evaluate_expression_json, internal_set_expression_json)
|
||||
internal_evaluate_expression_json, internal_set_expression_json, internal_get_exception_details_json)
|
||||
from _pydevd_bundle.pydevd_comm_constants import CMD_THREAD_SUSPEND, file_system_encoding
|
||||
from _pydevd_bundle.pydevd_constants import (get_current_thread_id, set_protocol, get_protocol,
|
||||
HTTP_JSON_PROTOCOL, JSON_PROTOCOL, STATE_RUN, IS_PY3K, DebugInfoHolder, dict_keys)
|
||||
|
|
@ -126,6 +126,15 @@ class PyDevdAPI(object):
|
|||
else:
|
||||
py_db.post_internal_command(internal_get_thread_stack, '*')
|
||||
|
||||
def request_exception_info_json(self, py_db, request, thread_id):
|
||||
py_db.post_method_as_internal_command(
|
||||
thread_id,
|
||||
internal_get_exception_details_json,
|
||||
request,
|
||||
thread_id,
|
||||
set_additional_thread_info=set_additional_thread_info,
|
||||
iter_visible_frames_info=py_db.cmd_factory._iter_visible_frames_info)
|
||||
|
||||
def request_step(self, py_db, thread_id, step_cmd_id):
|
||||
t = pydevd_find_thread_by_id(thread_id)
|
||||
if t:
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ each command has a format:
|
|||
'''
|
||||
|
||||
import itertools
|
||||
import linecache
|
||||
import os
|
||||
|
||||
from _pydev_bundle.pydev_imports import _queue
|
||||
|
|
@ -1076,6 +1077,93 @@ def internal_get_description(dbg, seq, thread_id, frame_id, expression):
|
|||
dbg.writer.add_command(cmd)
|
||||
|
||||
|
||||
def internal_get_exception_details_json(dbg, request, thread_id, set_additional_thread_info=None, iter_visible_frames_info=None):
|
||||
''' Fetch exception details
|
||||
'''
|
||||
try:
|
||||
thread = pydevd_find_thread_by_id(thread_id)
|
||||
additional_info = set_additional_thread_info(thread)
|
||||
topmost_frame = additional_info.get_topmost_frame(thread)
|
||||
|
||||
frames = []
|
||||
exc_type = None
|
||||
exc_desc = None
|
||||
if topmost_frame is not None:
|
||||
frame_id_to_lineno = {}
|
||||
try:
|
||||
trace_obj = None
|
||||
frame = topmost_frame
|
||||
while frame is not None:
|
||||
if frame.f_code.co_name == 'do_wait_suspend' and frame.f_code.co_filename.endswith('pydevd.py'):
|
||||
arg = frame.f_locals.get('arg', None)
|
||||
if arg is not None:
|
||||
exc_type, exc_desc, trace_obj = arg
|
||||
break
|
||||
frame = frame.f_back
|
||||
|
||||
while trace_obj.tb_next is not None:
|
||||
trace_obj = trace_obj.tb_next
|
||||
|
||||
info = dbg.suspended_frames_manager.get_topmost_frame_and_frame_id_to_line(thread_id)
|
||||
if info is not None:
|
||||
topmost_frame, frame_id_to_lineno = info
|
||||
|
||||
if trace_obj is not None:
|
||||
for frame_id, frame, method_name, filename_in_utf8, lineno in iter_visible_frames_info(
|
||||
dbg, trace_obj.tb_frame, frame_id_to_lineno):
|
||||
|
||||
line_text = linecache.getline(filename_in_utf8, lineno)
|
||||
|
||||
# Never filter out plugin frames!
|
||||
if not getattr(frame, 'IS_PLUGIN_FRAME', False):
|
||||
if not dbg.in_project_scope(filename_in_utf8):
|
||||
if not dbg.get_use_libraries_filter():
|
||||
continue
|
||||
frames.append((filename_in_utf8, lineno, method_name, line_text))
|
||||
finally:
|
||||
topmost_frame = None
|
||||
|
||||
if exc_desc is not None:
|
||||
name = exc_desc.__class__.__name__
|
||||
description = '%s' % (exc_desc,)
|
||||
else:
|
||||
name = 'exception: type unknown'
|
||||
description = 'exception: no description'
|
||||
|
||||
stack_str = ''.join(traceback.format_list(frames))
|
||||
|
||||
# This is an extra bit of data used by Visual Studio
|
||||
source_path = frames[0][0] if frames else ''
|
||||
|
||||
# TODO: breakMode is set to always. This should be retrieved from exception
|
||||
# breakpoint settings for that exception, or its parent chain. Currently json
|
||||
# support for setExceptionBreakpoint is not implemented.
|
||||
response = pydevd_schema.ExceptionInfoResponse(
|
||||
request_seq=request.seq,
|
||||
success=True,
|
||||
command='exceptionInfo',
|
||||
body=pydevd_schema.ExceptionInfoResponseBody(
|
||||
exceptionId=name,
|
||||
description=description,
|
||||
breakMode=pydevd_schema.ExceptionBreakMode.ALWAYS,
|
||||
details=pydevd_schema.ExceptionDetails(
|
||||
message=description,
|
||||
typeName=name,
|
||||
stackTrace=stack_str,
|
||||
source=source_path
|
||||
)
|
||||
)
|
||||
)
|
||||
except:
|
||||
exc = get_exception_traceback_str()
|
||||
response = pydevd_base_schema.build_response(request, kwargs={
|
||||
'success': False,
|
||||
'message': exc,
|
||||
'body':{}
|
||||
})
|
||||
dbg.writer.add_command(NetCommand(CMD_RETURN, 0, response, is_json=True))
|
||||
|
||||
|
||||
class InternalGetBreakpointException(InternalThreadCommand):
|
||||
''' Send details of exception raised while evaluating conditional breakpoint '''
|
||||
|
||||
|
|
|
|||
|
|
@ -371,6 +371,15 @@ class _PyDevJsonCommandProcessor(object):
|
|||
fmt = fmt.to_dict()
|
||||
self.api.request_stack(py_db, request.seq, thread_id, fmt)
|
||||
|
||||
def on_exceptioninfo_request(self, py_db, request):
|
||||
'''
|
||||
:param ExceptionInfoRequest request:
|
||||
'''
|
||||
# : :type exception_into_arguments: ExceptionInfoArguments
|
||||
exception_into_arguments = request.arguments
|
||||
thread_id = exception_into_arguments.threadId
|
||||
self.api.request_exception_info_json(py_db, request, thread_id)
|
||||
|
||||
def on_scopes_request(self, py_db, request):
|
||||
'''
|
||||
Scopes are the top-level items which appear for a frame (so, we receive the frame id
|
||||
|
|
|
|||
|
|
@ -994,6 +994,33 @@ def test_evaluate(case_setup):
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_exception_details(case_setup):
|
||||
with case_setup.test_file('_debugger_case_exceptions.py') as writer:
|
||||
json_facade = JsonFacade(writer)
|
||||
|
||||
writer.write_set_protocol('http_json')
|
||||
writer.write_add_exception_breakpoint_with_policy(
|
||||
'IndexError',
|
||||
notify_on_handled_exceptions=2, # Notify only once
|
||||
notify_on_unhandled_exceptions=0,
|
||||
ignore_libraries=1
|
||||
)
|
||||
|
||||
json_facade.write_make_initial_run()
|
||||
hit = writer.wait_for_breakpoint_hit(REASON_CAUGHT_EXCEPTION)
|
||||
|
||||
exc_info_request = json_facade.write_request(
|
||||
pydevd_schema.ExceptionInfoRequest(pydevd_schema.ExceptionInfoArguments(hit.thread_id)))
|
||||
exc_info_response = json_facade.wait_for_response(exc_info_request)
|
||||
body = exc_info_response.body
|
||||
assert body.exceptionId.endswith('IndexError')
|
||||
assert body.description == 'foo'
|
||||
assert body.details.kwargs['source'] == writer.TEST_FILE
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
|
||||
writer.finished_ok = True
|
||||
|
||||
@pytest.mark.skipif(IS_JYTHON, reason='Flaky on Jython.')
|
||||
def test_path_translation_and_source_reference(case_setup):
|
||||
|
||||
|
|
|
|||
|
|
@ -12,12 +12,10 @@ import json
|
|||
import os
|
||||
import platform
|
||||
import pydevd_file_utils
|
||||
import re
|
||||
import site
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
try:
|
||||
import urllib
|
||||
|
|
@ -1882,72 +1880,21 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
if request is not None:
|
||||
self.send_response(request)
|
||||
|
||||
def _parse_exception_details(self, exc_xml, include_stack=True):
|
||||
exc_name = None
|
||||
exc_desc = None
|
||||
exc_source = None
|
||||
exc_stack = None
|
||||
try:
|
||||
xml = self.parse_xml_response(exc_xml)
|
||||
re_name = r"[\'\"](.*)[\'\"]"
|
||||
exc_type = xml.thread['exc_type']
|
||||
exc_desc = xml.thread['exc_desc']
|
||||
try:
|
||||
exc_name = re.findall(re_name, exc_type)[0]
|
||||
except IndexError:
|
||||
exc_name = exc_type
|
||||
|
||||
if include_stack:
|
||||
xframes = list(xml.thread.frame)
|
||||
frame_data = []
|
||||
for f in xframes:
|
||||
file_path = unquote_xml_path(f['file'])
|
||||
if not self.internals_filter.is_internal_path(file_path) \
|
||||
and self._should_debug(file_path):
|
||||
line_no = int(f['line'])
|
||||
func_name = unquote(f['name'])
|
||||
if _util.is_py34():
|
||||
# NOTE: In 3.4.* format_list requires the text
|
||||
# to be passed in the tuple list.
|
||||
line_text = _util.get_line_for_traceback(file_path,
|
||||
line_no)
|
||||
frame_data.append((file_path, line_no,
|
||||
func_name, line_text))
|
||||
else:
|
||||
frame_data.append((file_path, line_no,
|
||||
func_name, None))
|
||||
|
||||
exc_stack = ''.join(traceback.format_list(frame_data))
|
||||
exc_source = unquote_xml_path(xframes[0]['file'])
|
||||
if self.internals_filter.is_internal_path(exc_source) or \
|
||||
not self._should_debug(exc_source):
|
||||
exc_source = None
|
||||
except Exception:
|
||||
exc_name = 'BaseException'
|
||||
exc_desc = 'exception: no description'
|
||||
|
||||
return exc_name, exc_desc, exc_source, exc_stack
|
||||
|
||||
@async_handler
|
||||
def on_exceptionInfo(self, request, args):
|
||||
# TODO: docstring
|
||||
pyd_tid = self.thread_map.to_pydevd(args['threadId'])
|
||||
|
||||
cmdid = pydevd_comm.CMD_GET_EXCEPTION_DETAILS
|
||||
_, _, resp_args = yield self.pydevd_request(cmdid, pyd_tid)
|
||||
name, description, source, stack = \
|
||||
self._parse_exception_details(resp_args)
|
||||
pydevd_request = copy.deepcopy(request)
|
||||
del pydevd_request['seq'] # A new seq should be created for pydevd.
|
||||
pydevd_request['arguments']['threadId'] = pyd_tid
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_GET_EXCEPTION_DETAILS,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
|
||||
self.send_response(
|
||||
request,
|
||||
exceptionId=name,
|
||||
description=description,
|
||||
breakMode=self.exceptions_mgr.get_break_mode(name),
|
||||
details={'typeName': name,
|
||||
'message': description,
|
||||
'stackTrace': stack,
|
||||
'source': source},
|
||||
)
|
||||
body = resp_args['body']
|
||||
body['breakMode'] = self.exceptions_mgr.get_break_mode(body['exceptionId'])
|
||||
self.send_response(request, **body)
|
||||
|
||||
@async_handler
|
||||
def on_completions(self, request, args):
|
||||
|
|
@ -2126,10 +2073,20 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
reason not in ['step', 'exception', 'breakpoint']
|
||||
|
||||
if reason == 'exception':
|
||||
cmdid = pydevd_comm.CMD_GET_EXCEPTION_DETAILS
|
||||
_, _, resp_args = yield self.pydevd_request(cmdid, pyd_tid)
|
||||
exc_name, exc_desc, _, _ = \
|
||||
self._parse_exception_details(resp_args, include_stack=False)
|
||||
pydevd_request = {
|
||||
'type': 'request',
|
||||
'command': 'exceptionInfo',
|
||||
'arguments': {
|
||||
'threadId': pyd_tid
|
||||
},
|
||||
}
|
||||
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_GET_EXCEPTION_DETAILS,
|
||||
pydevd_request,
|
||||
is_json=True)
|
||||
exc_name = resp_args['body']['exceptionId']
|
||||
exc_desc = resp_args['body']['description']
|
||||
|
||||
if not self.debug_options.get('BREAK_SYSTEMEXIT_ZERO', False):
|
||||
# SystemExit is qualified on Python 2, and unqualified on Python 3
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue