mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
parent
d03a2b044c
commit
62f03a2760
6 changed files with 86 additions and 25 deletions
|
|
@ -769,7 +769,15 @@ class NetCommandFactory:
|
|||
except:
|
||||
return self.make_error_message(0, get_exception_traceback_str())
|
||||
|
||||
def make_thread_stack_str(self, frame):
|
||||
def make_thread_stack_str(self, frame, frame_to_lineno=None):
|
||||
'''
|
||||
:param frame_to_lineno:
|
||||
If available, the line number for the frame will be gotten from this dict,
|
||||
otherwise frame.f_lineno will be used (needed for unhandled exceptions as
|
||||
the place where we report may be different from the place where it's raised).
|
||||
'''
|
||||
if frame_to_lineno is None:
|
||||
frame_to_lineno = {}
|
||||
make_valid_xml_value = pydevd_xml.make_valid_xml_value
|
||||
cmd_text_list = []
|
||||
append = cmd_text_list.append
|
||||
|
|
@ -801,7 +809,7 @@ class NetCommandFactory:
|
|||
|
||||
# print("file is ", filename_in_utf8)
|
||||
|
||||
lineno = str(curr_frame.f_lineno)
|
||||
lineno = frame_to_lineno.get(curr_frame, curr_frame.f_lineno)
|
||||
# print("line is ", lineno)
|
||||
|
||||
# Note: variables are all gotten 'on-demand'.
|
||||
|
|
@ -822,6 +830,7 @@ class NetCommandFactory:
|
|||
stop_reason=None,
|
||||
message=None,
|
||||
suspend_type="trace",
|
||||
frame_to_lineno=None
|
||||
):
|
||||
"""
|
||||
:return tuple(str,str):
|
||||
|
|
@ -860,17 +869,17 @@ class NetCommandFactory:
|
|||
if suspend_type is not None:
|
||||
append(' suspend_type="%s"' % (suspend_type,))
|
||||
append('>')
|
||||
thread_stack_str = self.make_thread_stack_str(frame)
|
||||
thread_stack_str = self.make_thread_stack_str(frame, frame_to_lineno)
|
||||
append(thread_stack_str)
|
||||
append("</thread></xml>")
|
||||
|
||||
return ''.join(cmd_text_list), thread_stack_str
|
||||
|
||||
def make_thread_suspend_message(self, thread_id, frame, stop_reason, message, suspend_type):
|
||||
def make_thread_suspend_message(self, thread_id, frame, stop_reason, message, suspend_type, frame_to_lineno=None):
|
||||
try:
|
||||
|
||||
thread_suspend_str, thread_stack_str = self.make_thread_suspend_str(
|
||||
thread_id, frame, stop_reason, message, suspend_type)
|
||||
thread_id, frame, stop_reason, message, suspend_type, frame_to_lineno=frame_to_lineno)
|
||||
cmd = NetCommand(CMD_THREAD_SUSPEND, 0, thread_suspend_str)
|
||||
cmd.thread_stack_str = thread_stack_str
|
||||
return cmd
|
||||
|
|
|
|||
|
|
@ -790,10 +790,14 @@ class PyDB:
|
|||
finally:
|
||||
self._main_lock.release()
|
||||
|
||||
def do_wait_suspend(self, thread, frame, event, arg, suspend_type="trace", send_suspend_message=True): #@UnusedVariable
|
||||
def do_wait_suspend(self, thread, frame, event, arg, suspend_type="trace", send_suspend_message=True, is_unhandled_exception=False): #@UnusedVariable
|
||||
""" busy waits until the thread state changes to RUN
|
||||
it expects thread's state as attributes of the thread.
|
||||
Upon running, processes any outstanding Stepping commands.
|
||||
|
||||
:param is_unhandled_exception:
|
||||
If True we should use the line of the exception instead of the current line in the frame
|
||||
as the paused location on the top-level frame (exception info must be passed on 'arg').
|
||||
"""
|
||||
self.process_internal_commands()
|
||||
thread_stack_str = '' # @UnusedVariable -- this is here so that `make_get_thread_stack_message`
|
||||
|
|
@ -801,7 +805,15 @@ class PyDB:
|
|||
|
||||
if send_suspend_message:
|
||||
message = thread.additional_info.pydev_message
|
||||
cmd = self.cmd_factory.make_thread_suspend_message(get_thread_id(thread), frame, thread.stop_reason, message, suspend_type)
|
||||
frame_to_lineno = {}
|
||||
if is_unhandled_exception:
|
||||
# arg must be the exception info (tuple(exc_type, exc, traceback))
|
||||
tb = arg[2]
|
||||
while tb is not None:
|
||||
frame_to_lineno[tb.tb_frame] = tb.tb_lineno
|
||||
tb = tb.tb_next
|
||||
cmd = self.cmd_factory.make_thread_suspend_message(get_thread_id(thread), frame, thread.stop_reason, message, suspend_type, frame_to_lineno=frame_to_lineno)
|
||||
frame_to_lineno.clear()
|
||||
thread_stack_str = cmd.thread_stack_str # @UnusedVariable -- `make_get_thread_stack_message` uses it later.
|
||||
self.writer.add_command(cmd)
|
||||
|
||||
|
|
@ -926,7 +938,7 @@ class PyDB:
|
|||
try:
|
||||
add_exception_to_frame(frame, arg)
|
||||
self.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
|
||||
self.do_wait_suspend(thread, frame, 'exception', arg, "trace")
|
||||
self.do_wait_suspend(thread, frame, 'exception', arg, "trace", is_unhandled_exception=True)
|
||||
except:
|
||||
pydev_log.error("We've got an error while stopping in post-mortem: %s\n" % (arg[0],))
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -284,18 +284,6 @@ class DebuggerRunner(object):
|
|||
ret = writer.update_command_line_args(ret) # Provide a hook for the writer
|
||||
return args + ret
|
||||
|
||||
def check_case_simple(self, target, test_file):
|
||||
|
||||
class WriterThreadCase(AbstractWriterThread):
|
||||
|
||||
TEST_FILE = _get_debugger_test_file(test_file)
|
||||
|
||||
def run(self):
|
||||
return target(self)
|
||||
|
||||
with self.check_case(WriterThreadCase) as writer:
|
||||
yield writer
|
||||
|
||||
@contextmanager
|
||||
def check_case(self, writer_class):
|
||||
if callable(writer_class):
|
||||
|
|
@ -503,6 +491,16 @@ class AbstractWriterThread(threading.Thread):
|
|||
dirname = os.path.dirname(dirname)
|
||||
return os.path.abspath(os.path.join(dirname, 'pydevd.py'))
|
||||
|
||||
def get_line_index_with_content(self, line_content):
|
||||
'''
|
||||
:return the line index which has the given content (1-based).
|
||||
'''
|
||||
with open(self.TEST_FILE, 'r') as stream:
|
||||
for i_line, line in enumerate(stream):
|
||||
if line_content in line:
|
||||
return i_line + 1
|
||||
raise AssertionError('Did not find: %s in %s' % (line_content, self.TEST_FILE))
|
||||
|
||||
def get_cwd(self):
|
||||
return os.path.dirname(self.get_pydevd_file())
|
||||
|
||||
|
|
@ -524,7 +522,7 @@ class AbstractWriterThread(threading.Thread):
|
|||
meaning = ID_TO_MEANING.get(re.search(r'\d+', s).group(), '')
|
||||
if meaning:
|
||||
meaning += ': '
|
||||
|
||||
|
||||
self.log.append('write: %s%s' % (meaning, s,))
|
||||
|
||||
if SHOW_WRITES_AND_READS:
|
||||
|
|
@ -885,6 +883,9 @@ class AbstractWriterThread(threading.Thread):
|
|||
def wait_for_list_threads(self, seq):
|
||||
return self.wait_for_message(lambda msg:msg.startswith('502\t%s' % (seq,)))
|
||||
|
||||
def wait_for_get_thread_stack_message(self):
|
||||
return self.wait_for_message(lambda msg:msg.startswith('%s\t' % (CMD_GET_THREAD_STACK,)))
|
||||
|
||||
def wait_for_message(self, accept_message, unquote_msg=True, expect_xml=True):
|
||||
import untangle
|
||||
from io import StringIO
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
from contextlib import contextmanager
|
||||
|
||||
|
||||
@contextmanager
|
||||
def something():
|
||||
yield
|
||||
|
||||
|
||||
with something():
|
||||
raise ValueError('TEST SUCEEDED') # break line on unhandled exception
|
||||
print('a')
|
||||
print('b')
|
||||
|
|
@ -1212,6 +1212,33 @@ def test_case_set_next_statement(case_setup):
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_unhandled_exceptions_get_stack(case_setup_unhandled_exceptions):
|
||||
|
||||
with case_setup_unhandled_exceptions.test_file(
|
||||
'_debugger_case_unhandled_exception_get_stack.py') as writer:
|
||||
|
||||
writer.write_add_exception_breakpoint_with_policy('Exception', "0", "1", "0")
|
||||
writer.write_make_initial_run()
|
||||
|
||||
hit = writer.wait_for_breakpoint_hit(REASON_UNCAUGHT_EXCEPTION)
|
||||
writer.write_get_thread_stack(hit.thread_id)
|
||||
|
||||
msg = writer.wait_for_get_thread_stack_message()
|
||||
files = [frame['file'] for frame in msg.thread.frame]
|
||||
assert msg.thread['id'] == hit.thread_id
|
||||
if not files[0].endswith('_debugger_case_unhandled_exception_get_stack.py'):
|
||||
raise AssertionError('Expected to find _debugger_case_unhandled_exception_get_stack.py in files[0]. Found: %s' % ('\n'.join(files),))
|
||||
|
||||
assert len(msg.thread.frame) == 0 # No back frames (stopped in main).
|
||||
assert msg.thread.frame['name'] == '<module>'
|
||||
assert msg.thread.frame['line'] == str(writer.get_line_index_with_content('break line on unhandled exception'))
|
||||
|
||||
writer.write_run_thread(hit.thread_id)
|
||||
|
||||
writer.log.append('Marking finished ok.')
|
||||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif(not IS_CPYTHON, reason='Only for Python.')
|
||||
def test_case_get_next_statement_targets(case_setup):
|
||||
with case_setup.test_file('_debugger_case_get_next_statement_targets.py') as writer:
|
||||
|
|
@ -1739,7 +1766,7 @@ def test_case_get_thread_stack(case_setup):
|
|||
|
||||
for request_thread_id in thread_id_to_name:
|
||||
writer.write_get_thread_stack(request_thread_id)
|
||||
msg = writer.wait_for_message(lambda msg:msg.startswith('%s\t' % (CMD_GET_THREAD_STACK,)))
|
||||
msg = writer.wait_for_get_thread_stack_message()
|
||||
files = [frame['file'] for frame in msg.thread.frame]
|
||||
assert msg.thread['id'] == request_thread_id
|
||||
if not files[0].endswith('_debugger_case_get_thread_stack.py'):
|
||||
|
|
|
|||
|
|
@ -39,15 +39,15 @@ class DummyPyDb(PyDB):
|
|||
def __init__(self):
|
||||
PyDB.__init__(self, set_as_global=False)
|
||||
|
||||
def do_wait_suspend(self, thread, frame, event, arg, suspend_type="trace", send_suspend_message=True):
|
||||
def do_wait_suspend(
|
||||
self, thread, frame, event, arg, *args, **kwargs):
|
||||
from _pydevd_bundle.pydevd_constants import STATE_RUN
|
||||
info = thread.additional_info
|
||||
info.pydev_step_cmd = -1
|
||||
info.pydev_step_stop = None
|
||||
info.pydev_state = STATE_RUN
|
||||
|
||||
return PyDB.do_wait_suspend(
|
||||
self, thread, frame, event, arg, suspend_type=suspend_type, send_suspend_message=send_suspend_message)
|
||||
return PyDB.do_wait_suspend(self, thread, frame, event, arg, *args, **kwargs)
|
||||
|
||||
|
||||
class _TraceTopLevel(object):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue