Skip to content

Commit

Permalink
Make errors with conditional breakpoints clearer. Fixes microsoft#893
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioz committed Apr 14, 2022
1 parent 87facf1 commit 3d9811e
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 28 deletions.
3 changes: 2 additions & 1 deletion src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ def set_gui_event_loop(self, py_db, gui_event_loop):
py_db._gui_event_loop = gui_event_loop

def send_error_message(self, py_db, msg):
sys.stderr.write('pydevd: %s\n' % (msg,))
cmd = py_db.cmd_factory.make_warning_message('pydevd: %s\n' % (msg,))
py_db.writer.add_command(cmd)

def set_show_return_values(self, py_db, show_return_values):
if show_return_values:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ProcessEvent, Scope, ScopesResponseBody, SetExpressionResponseBody,
SetVariableResponseBody, SourceBreakpoint, SourceResponseBody,
VariablesResponseBody, SetBreakpointsResponseBody, Response,
Capabilities, PydevdAuthorizeRequest, Request, StepInTargetsResponse, StepInTarget,
Capabilities, PydevdAuthorizeRequest, Request,
StepInTargetsResponseBody, SetFunctionBreakpointsResponseBody, BreakpointEvent,
BreakpointEventBody)
from _pydevd_bundle.pydevd_api import PyDevdAPI
Expand All @@ -35,7 +35,6 @@
from _pydevd_bundle.pydevd_comm import internal_get_step_in_targets_json
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
from _pydevd_bundle.pydevd_thread_lifecycle import pydevd_find_thread_by_id
from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding


def _convert_rules_to_exclude_filters(rules, on_error):
Expand Down
11 changes: 8 additions & 3 deletions src/debugpy/_vendored/pydevd/pydevd.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import atexit
import dis
import io
from collections import defaultdict
from contextlib import contextmanager
from functools import partial
Expand Down Expand Up @@ -881,10 +882,14 @@ def handle_breakpoint_condition(self, info, pybreakpoint, new_frame):
return eval(condition, new_frame.f_globals, new_frame.f_locals)
except Exception as e:
if not isinstance(e, self.skip_print_breakpoint_exception):
sys.stderr.write('Error while evaluating expression: %s\n' % (condition,))

stack_trace = io.StringIO()
etype, value, tb = sys.exc_info()
traceback.print_exception(etype, value, tb.tb_next)
traceback.print_exception(etype, value, tb.tb_next, file=stack_trace)

msg = 'Error while evaluating expression in conditional breakpoint: %s\n%s' % (
condition, stack_trace.getvalue())
api = PyDevdAPI()
api.send_error_message(self, msg)

if not isinstance(e, self.skip_suspend_on_breakpoint_exception):
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ def __init__(self, sock):
self._queue = Queue()
self._kill = False
self.accept_xml_messages = True
self.on_message_found = lambda msg: None

def set_messages_timeout(self, timeout):
self.MESSAGES_TIMEOUT = timeout
Expand All @@ -208,6 +209,7 @@ def get_next_message(self, context_message, timeout=None):
timeout = self.MESSAGES_TIMEOUT
try:
msg = self._queue.get(block=True, timeout=timeout)
self.on_message_found(msg)
except:
raise TimeoutError('No message was written in %s seconds. Error message:\n%s' % (timeout, context_message,))
else:
Expand Down
53 changes: 31 additions & 22 deletions src/debugpy/_vendored/pydevd/tests_python/test_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def test_case_2(case_setup):
def test_case_breakpoint_condition_exc(case_setup, skip_suspend_on_breakpoint_exception, skip_print_breakpoint_exception):

msgs_in_stderr = (
'Error while evaluating expression: i > 5',
'Error while evaluating expression in conditional breakpoint: i > 5',
'Traceback (most recent call last):',
'File "<string>", line 1, in <module>',
)
Expand All @@ -140,44 +140,53 @@ def _ignore_stderr_line(line):

return False

def additional_output_checks(stdout, stderr):
original_additional_output_checks(stdout, stderr)
if skip_print_breakpoint_exception in ([], ['ValueError']):
for msg in msgs_in_stderr:
assert msg in stderr

for msg in msgs_one_in_stderr:
if msg in stderr:
break
else:
raise AssertionError('Did not find any of: %s in stderr: %s' % (
msgs_one_in_stderr, stderr))
else:
for msg in msgs_in_stderr + msgs_one_in_stderr:
assert msg not in stderr

with case_setup.test_file('_debugger_case_breakpoint_condition_exc.py') as writer:

original_ignore_stderr_line = writer._ignore_stderr_line
writer._ignore_stderr_line = _ignore_stderr_line

original_additional_output_checks = writer.additional_output_checks
writer.additional_output_checks = additional_output_checks

writer.write_suspend_on_breakpoint_exception(skip_suspend_on_breakpoint_exception, skip_print_breakpoint_exception)

expect_print = skip_print_breakpoint_exception in ([], ['ValueError'])
expect_suspend = skip_suspend_on_breakpoint_exception in ([], ['ValueError'])

breakpoint_id = writer.write_add_breakpoint(
writer.get_line_index_with_content('break here'), 'Call', condition='i > 5')

if not expect_print:
_original = writer.reader_thread.on_message_found

def on_message_found(found_msg):
for msg in msgs_in_stderr + msgs_one_in_stderr:
assert msg not in found_msg

writer.reader_thread.on_message_found = on_message_found

writer.write_make_initial_run()

if skip_suspend_on_breakpoint_exception in ([], ['ValueError']):
def check_error_msg(stderr):
for msg in msgs_in_stderr:
assert msg in stderr

for msg in msgs_one_in_stderr:
if msg in stderr:
break
else:
raise AssertionError('Did not find any of: %s in stderr: %s' % (
msgs_one_in_stderr, stderr))

if expect_print:
msg, ctx = writer.wait_for_output()
check_error_msg(msg.replace('&gt;', '>'))

if expect_suspend:
writer.wait_for_message(CMD_GET_BREAKPOINT_EXCEPTION)
hit = writer.wait_for_breakpoint_hit()
writer.write_run_thread(hit.thread_id)

if IS_JYTHON:
# Jython will break twice.
if skip_suspend_on_breakpoint_exception in ([], ['ValueError']):
if expect_suspend:
writer.wait_for_message(CMD_GET_BREAKPOINT_EXCEPTION)
hit = writer.wait_for_breakpoint_hit()
writer.write_run_thread(hit.thread_id)
Expand Down
27 changes: 27 additions & 0 deletions src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,33 @@ def test_case_json_hit_count_and_step(case_setup):
writer.finished_ok = True


def test_case_json_hit_condition_error(case_setup):
with case_setup.test_file('_debugger_case_hit_count.py') as writer:
json_facade = JsonFacade(writer)

json_facade.write_launch()
bp = writer.get_line_index_with_content('before loop line')
json_facade.write_set_breakpoints(
[bp],
line_to_info={
bp: {'condition': 'range.range.range'}
})
json_facade.write_make_initial_run()

def accept_message(msg):
if msg.body.category == 'important':
if 'Error while evaluating expression in conditional breakpoint' in msg.body.output:
return True
return False

json_facade.wait_for_json_message(OutputEvent, accept_message=accept_message)

json_facade.wait_for_thread_stopped(line=bp)
json_facade.write_continue()

writer.finished_ok = True


def test_case_process_event(case_setup):
with case_setup.test_file('_debugger_case_change_breaks.py') as writer:
json_facade = JsonFacade(writer)
Expand Down

0 comments on commit 3d9811e

Please sign in to comment.