mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Fix #18: Set Next Statement
This commit is contained in:
parent
19aaa3b178
commit
2bb23bc698
7 changed files with 207 additions and 48 deletions
|
|
@ -77,7 +77,8 @@ def log_pydevd_msg(cmdid, seq, args, inbound,
|
|||
else:
|
||||
cmdname = '???'
|
||||
cmd = '{} ({})'.format(cmdid, cmdname)
|
||||
args = args.replace('\n', '\\n')
|
||||
if isinstance(args, bytes) or isinstance(args, str):
|
||||
args = args.replace('\n', '\\n')
|
||||
msg = '{}{:28} [{:>10}]: |{}|'.format(prefix, cmd, seq, args)
|
||||
log(msg)
|
||||
|
||||
|
|
@ -420,18 +421,28 @@ try:
|
|||
import dis
|
||||
except ImportError:
|
||||
def get_code_lines(code):
|
||||
return None
|
||||
raise NotImplementedError
|
||||
else:
|
||||
def get_code_lines(code):
|
||||
# First, get all line starts for this code object. This does not include
|
||||
# bodies of nested class and function definitions, as they have their
|
||||
# own objects.
|
||||
for _, lineno in dis.findlinestarts(code):
|
||||
yield lineno
|
||||
if not isinstance(code, types.CodeType):
|
||||
path = code
|
||||
with open(path) as f:
|
||||
src = f.read()
|
||||
code = compile(src, path, 'exec', 0, dont_inherit=True)
|
||||
return get_code_lines(code)
|
||||
|
||||
# For nested class and function definitions, their respective code objects
|
||||
# are constants referenced by this object.
|
||||
for const in code.co_consts:
|
||||
if isinstance(const, types.CodeType) and const.co_filename == code.co_filename:
|
||||
for lineno in get_code_lines(const):
|
||||
yield lineno
|
||||
def iterate():
|
||||
# First, get all line starts for this code object. This does not include
|
||||
# bodies of nested class and function definitions, as they have their
|
||||
# own objects.
|
||||
for _, lineno in dis.findlinestarts(code):
|
||||
yield lineno
|
||||
|
||||
# For nested class and function definitions, their respective code objects
|
||||
# are constants referenced by this object.
|
||||
for const in code.co_consts:
|
||||
if isinstance(const, types.CodeType) and const.co_filename == code.co_filename:
|
||||
for lineno in get_code_lines(const):
|
||||
yield lineno
|
||||
|
||||
return iterate()
|
||||
|
|
@ -22,6 +22,8 @@ with vendored('pydevd'):
|
|||
pydevd_constants.CYTHON_SUPPORTED = False
|
||||
# We limit representation size in our representation provider when needed.
|
||||
pydevd_constants.MAXIMUM_VARIABLE_REPRESENTATION_SIZE = 2**32
|
||||
# We want CMD_SET_NEXT_STATEMENT to have a response indicating success or failure.
|
||||
pydevd_constants.GOTO_HAS_RESPONSE = True
|
||||
|
||||
|
||||
# Now make sure all the top-level modules and packages in pydevd are
|
||||
|
|
|
|||
|
|
@ -115,6 +115,9 @@ USE_LIB_COPY = SUPPORT_GEVENT and \
|
|||
INTERACTIVE_MODE_AVAILABLE = sys.platform in ('darwin', 'win32') or os.getenv('DISPLAY') is not None
|
||||
IS_PYCHARM = False
|
||||
|
||||
# If True, CMD_SET_NEXT_STATEMENT and CMD_RUN_TO_LINE commands have responses indicating success or failure.
|
||||
GOTO_HAS_RESPONSE = IS_PYCHARM
|
||||
|
||||
LOAD_VALUES_ASYNC = os.getenv('PYDEVD_LOAD_VALUES_ASYNC', 'False') == 'True'
|
||||
DEFAULT_VALUE = "__pydevd_value_async"
|
||||
ASYNC_EVAL_TIMEOUT_SEC = 60
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ from _pydevd_bundle.pydevd_comm_constants import (CMD_THREAD_SUSPEND, CMD_STEP_I
|
|||
from _pydevd_bundle.pydevd_constants import (IS_JYTH_LESS25, IS_PYCHARM, get_thread_id, get_current_thread_id,
|
||||
dict_keys, dict_iter_items, DebugInfoHolder, PYTHON_SUSPEND, STATE_SUSPEND, STATE_RUN, get_frame,
|
||||
clear_cached_thread_id, INTERACTIVE_MODE_AVAILABLE, SHOW_DEBUG_INFO_ENV, IS_PY34_OR_GREATER, IS_PY2, NULL,
|
||||
NO_FTRACE)
|
||||
NO_FTRACE, GOTO_HAS_RESPONSE)
|
||||
from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init
|
||||
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE
|
||||
from _pydevd_bundle.pydevd_extension_api import DebuggerEventHandler
|
||||
|
|
@ -1181,7 +1181,7 @@ class PyDB(object):
|
|||
if curr_func_name in ('?', '<module>'):
|
||||
curr_func_name = ''
|
||||
|
||||
if curr_func_name == func_name:
|
||||
if func_name == '*' or curr_func_name == func_name:
|
||||
line = next_line
|
||||
frame.f_trace = self.trace_dispatch
|
||||
frame.f_lineno = line
|
||||
|
|
@ -1302,7 +1302,7 @@ class PyDB(object):
|
|||
except ValueError as e:
|
||||
response_msg = "%s" % e
|
||||
finally:
|
||||
if IS_PYCHARM:
|
||||
if GOTO_HAS_RESPONSE:
|
||||
seq = info.pydev_message
|
||||
cmd = self.cmd_factory.make_set_next_stmnt_status_message(seq, stop, response_msg)
|
||||
self.writer.add_command(cmd)
|
||||
|
|
|
|||
|
|
@ -239,6 +239,9 @@ class RequestFailure(Exception):
|
|||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.message)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, RequestFailure):
|
||||
return NotImplemented
|
||||
|
|
|
|||
|
|
@ -503,7 +503,8 @@ class PydevdSocket(object):
|
|||
raise EOFError
|
||||
seq, s = self.make_packet(cmd_id, args)
|
||||
_util.log_pydevd_msg(cmd_id, seq, args, inbound=False)
|
||||
os.write(self.pipe_w, s.encode('utf8'))
|
||||
with self.lock:
|
||||
os.write(self.pipe_w, s.encode('utf8'))
|
||||
|
||||
def pydevd_request(self, loop, cmd_id, args, is_json=False):
|
||||
'''
|
||||
|
|
@ -520,16 +521,16 @@ class PydevdSocket(object):
|
|||
seq, s = self.make_packet(cmd_id, args)
|
||||
_util.log_pydevd_msg(cmd_id, seq, args, inbound=False)
|
||||
fut = loop.create_future()
|
||||
|
||||
with self.lock:
|
||||
self.requests[seq] = loop, fut
|
||||
as_bytes = s
|
||||
if not isinstance(as_bytes, bytes):
|
||||
as_bytes = as_bytes.encode('utf-8')
|
||||
|
||||
if is_json:
|
||||
os.write(self.pipe_w, ('Content-Length:%s\r\n\r\n' % (len(as_bytes),)).encode('ascii'))
|
||||
|
||||
os.write(self.pipe_w, as_bytes)
|
||||
|
||||
return fut
|
||||
|
||||
|
||||
|
|
@ -1104,6 +1105,7 @@ INITIALIZE_RESPONSE = dict(
|
|||
supportsSetVariable=True,
|
||||
supportsValueFormattingOptions=True,
|
||||
supportTerminateDebuggee=True,
|
||||
supportsGotoTargetsRequest=False, # https://github.com/Microsoft/ptvsd/issues/1163
|
||||
exceptionBreakpointFilters=[
|
||||
{
|
||||
'filter': 'raised',
|
||||
|
|
@ -1347,6 +1349,8 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
self.frame_map = IDMap()
|
||||
self.var_map = IDMap()
|
||||
self.source_map = IDMap()
|
||||
self.goto_target_map = IDMap()
|
||||
self.current_goto_request = None
|
||||
self.enable_source_references = False
|
||||
self.next_var_ref = 0
|
||||
self._path_mappings = []
|
||||
|
|
@ -2233,6 +2237,34 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
self.pydevd_notify(pydevd_comm.CMD_STEP_RETURN, tid)
|
||||
self.send_response(request)
|
||||
|
||||
@async_handler
|
||||
def on_gotoTargets(self, request, args):
|
||||
path = args['source']['path']
|
||||
line = args['line']
|
||||
target_id = self.goto_target_map.to_vscode((path, line), autogen=True)
|
||||
self.send_response(request, targets=[{
|
||||
'id': target_id,
|
||||
'label': '{}:{}'.format(path, line),
|
||||
'line': line,
|
||||
}])
|
||||
|
||||
@async_handler
|
||||
def on_goto(self, request, args):
|
||||
if self.current_goto_request is not None:
|
||||
self.send_error_response(request, 'Already processing a "goto" request.')
|
||||
return
|
||||
|
||||
vsc_tid = args['threadId']
|
||||
target_id = args['targetId']
|
||||
|
||||
pyd_tid = self.thread_map.to_pydevd(vsc_tid)
|
||||
path, line = self.goto_target_map.to_pydevd(target_id)
|
||||
|
||||
self.current_goto_request = request
|
||||
self.pydevd_notify(
|
||||
pydevd_comm.CMD_SET_NEXT_STATEMENT,
|
||||
'{}\t{}\t*'.format(pyd_tid, line))
|
||||
|
||||
def _get_hit_condition_expression(self, hit_condition):
|
||||
"""Following hit condition values are supported
|
||||
|
||||
|
|
@ -2293,30 +2325,18 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
path = path.encode(sys.getfilesystemencoding())
|
||||
path = path_to_unicode(pydevd_file_utils.norm_file_to_server(path))
|
||||
|
||||
try:
|
||||
with open(path) as f:
|
||||
src = f.read()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
code = compile(src, path, 'exec', 0, dont_inherit=True)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
lines = sorted(_util.get_code_lines(code))
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
if lines:
|
||||
for bp in args['breakpoints']:
|
||||
line = bp['line']
|
||||
if line not in lines:
|
||||
# Adjust to the first preceding valid line.
|
||||
idx = bisect.bisect_left(lines, line)
|
||||
if idx > 0:
|
||||
bp['line'] = lines[idx - 1]
|
||||
try:
|
||||
lines = sorted(_util.get_code_lines(path))
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
for bp in args['breakpoints']:
|
||||
line = bp['line']
|
||||
if line not in lines:
|
||||
# Adjust to the first preceding valid line.
|
||||
idx = bisect.bisect_left(lines, line)
|
||||
if idx > 0:
|
||||
bp['line'] = lines[idx - 1]
|
||||
|
||||
_, _, resp_args = yield self.pydevd_request(
|
||||
pydevd_comm.CMD_SET_BREAK,
|
||||
|
|
@ -2573,7 +2593,30 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
@pydevd_events.handler(pydevd_comm.CMD_THREAD_SUSPEND)
|
||||
@async_handler
|
||||
def on_pydevd_thread_suspend(self, seq, args):
|
||||
pass
|
||||
xml = self.parse_xml_response(args)
|
||||
reason = int(xml.thread['stop_reason'])
|
||||
|
||||
# Normally, we rely on CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION instead,
|
||||
# but we only get this one in response to CMD_SET_NEXT_STATEMENT.
|
||||
if reason == pydevd_comm.CMD_SET_NEXT_STATEMENT:
|
||||
pyd_tid = xml.thread['id']
|
||||
vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=False)
|
||||
self.send_event(
|
||||
'stopped',
|
||||
reason='pause',
|
||||
threadId=vsc_tid,
|
||||
allThreadsStopped=True)
|
||||
|
||||
@pydevd_events.handler(pydevd_comm.CMD_THREAD_RUN)
|
||||
def on_pydevd_thread_run(self, seq, args):
|
||||
pyd_tid, reason = args.split('\t', 2)
|
||||
reason = int(reason)
|
||||
vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=False)
|
||||
|
||||
# Normally, we rely on CMD_THREAD_RESUME_SINGLE_NOTIFICATION instead,
|
||||
# but we only get this one in response to CMD_SET_NEXT_STATEMENT.
|
||||
if reason == pydevd_comm.CMD_SET_NEXT_STATEMENT:
|
||||
self.send_event('continued', threadId=vsc_tid)
|
||||
|
||||
@pydevd_events.handler(pydevd_comm_constants.CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION)
|
||||
@async_handler
|
||||
|
|
@ -2634,10 +2677,6 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
description=exc_desc,
|
||||
**extra)
|
||||
|
||||
@pydevd_events.handler(pydevd_comm.CMD_THREAD_RUN)
|
||||
def on_pydevd_thread_run(self, seq, args):
|
||||
pass # Ignore: only send continued on CMD_THREAD_RESUME_SINGLE_NOTIFICATION
|
||||
|
||||
@pydevd_events.handler(pydevd_comm_constants.CMD_THREAD_RESUME_SINGLE_NOTIFICATION)
|
||||
def on_pydevd_thread_resume_single_notification(self, seq, args):
|
||||
resumed_info = json.loads(args)
|
||||
|
|
@ -2677,3 +2716,16 @@ class VSCodeMessageProcessor(VSCLifecycleMsgProcessor):
|
|||
@pydevd_events.handler(pydevd_comm.CMD_PROCESS_CREATED)
|
||||
def on_pydevd_process_create(self, seq, args):
|
||||
pass
|
||||
|
||||
@pydevd_events.handler(pydevd_comm.CMD_SET_NEXT_STATEMENT)
|
||||
def on_pydevd_set_next_statement(self, seq, args):
|
||||
goto_request = self.current_goto_request
|
||||
assert goto_request is not None
|
||||
self.current_goto_request = None
|
||||
|
||||
success, message = args.split('\t', 2)
|
||||
|
||||
if success == 'True':
|
||||
self.send_response(goto_request)
|
||||
else:
|
||||
self.send_error_response(goto_request, message)
|
||||
|
|
|
|||
88
tests/func/test_step.py
Normal file
88
tests/func/test_step.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License. See LICENSE in the project root
|
||||
# for license information.
|
||||
|
||||
from __future__ import absolute_import, print_function, with_statement, unicode_literals
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.helpers import get_marked_line_numbers, print
|
||||
from tests.helpers.session import DebugSession
|
||||
from tests.helpers.timeline import Event
|
||||
from tests.helpers.pattern import ANY
|
||||
|
||||
|
||||
@pytest.mark.skip(reason='https://github.com/Microsoft/ptvsd/issues/1163')
|
||||
def test_set_next_statement(pyfile, run_as, start_method):
|
||||
@pyfile
|
||||
def code_to_debug():
|
||||
from dbgimporter import import_and_enable_debugger
|
||||
import_and_enable_debugger()
|
||||
|
||||
def func():
|
||||
print(1) #@inner1
|
||||
print(2) #@inner2
|
||||
print(3) #@outer3
|
||||
func()
|
||||
|
||||
line_numbers = get_marked_line_numbers(code_to_debug)
|
||||
print(line_numbers)
|
||||
|
||||
with DebugSession() as session:
|
||||
session.initialize(
|
||||
target=(run_as, code_to_debug),
|
||||
start_method=start_method,
|
||||
ignore_unobserved=[Event('continued')],
|
||||
)
|
||||
session.set_breakpoints(code_to_debug, [line_numbers['inner1']])
|
||||
session.start_debugging()
|
||||
|
||||
stop = session.wait_for_thread_stopped()
|
||||
frames = stop.stacktrace.body['stackFrames']
|
||||
line = frames[0]['line']
|
||||
assert line == line_numbers['inner1']
|
||||
|
||||
targets = session.send_request('gotoTargets', {
|
||||
'source': {'path': code_to_debug},
|
||||
'line': line_numbers['outer3'],
|
||||
}).wait_for_response().body['targets']
|
||||
|
||||
assert targets == [{
|
||||
'id': ANY.num,
|
||||
'label': ANY.str,
|
||||
'line': line_numbers['outer3']
|
||||
}]
|
||||
outer3_target = targets[0]['id']
|
||||
|
||||
with pytest.raises(Exception):
|
||||
session.send_request('goto', {
|
||||
'threadId': stop.thread_id,
|
||||
'targetId': outer3_target,
|
||||
}).wait_for_response()
|
||||
|
||||
targets = session.send_request('gotoTargets', {
|
||||
'source': {'path': code_to_debug},
|
||||
'line': line_numbers['inner2'],
|
||||
}).wait_for_response().body['targets']
|
||||
|
||||
assert targets == [{
|
||||
'id': ANY.num,
|
||||
'label': ANY.str,
|
||||
'line': line_numbers['inner2'],
|
||||
}]
|
||||
inner2_target = targets[0]['id']
|
||||
|
||||
session.send_request('goto', {
|
||||
'threadId': stop.thread_id,
|
||||
'targetId': inner2_target,
|
||||
}).wait_for_response()
|
||||
|
||||
session.wait_for_next(Event('continued'))
|
||||
|
||||
stop = session.wait_for_thread_stopped()
|
||||
frames = stop.stacktrace.body['stackFrames']
|
||||
line = frames[0]['line']
|
||||
assert line == line_numbers['inner2']
|
||||
|
||||
session.send_request('continue').wait_for_response()
|
||||
session.wait_for_exit()
|
||||
Loading…
Add table
Add a link
Reference in a new issue