Skip to content

Commit c419d5a

Browse files
committed
Don't stop on logpoints with conditions. Fixes #1146
1 parent 66f85dc commit c419d5a

6 files changed

Lines changed: 1942 additions & 2011 deletions

File tree

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_cython.c

Lines changed: 1870 additions & 1945 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_cython.pyx

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ cdef class PyDBFrame:
704704
cdef bint has_exception_breakpoints;
705705
cdef bint can_skip;
706706
cdef bint stop;
707+
cdef bint stop_on_plugin_breakpoint;
707708
cdef PyDBAdditionalThreadInfo info;
708709
cdef int step_cmd;
709710
cdef int line;
@@ -714,7 +715,6 @@ cdef class PyDBFrame:
714715
cdef dict breakpoints_for_file;
715716
cdef dict stop_info;
716717
cdef str curr_func_name;
717-
cdef bint exist_result;
718718
cdef dict frame_skips_cache;
719719
cdef object frame_cache_key;
720720
cdef tuple line_cache_key;
@@ -1036,13 +1036,12 @@ cdef class PyDBFrame:
10361036
# if DEBUG: print('NOT skipped: %s %s %s %s' % (frame.f_lineno, frame.f_code.co_name, event, frame.__class__.__name__))
10371037

10381038
try:
1039-
flag = False
1039+
stop_on_plugin_breakpoint = False
10401040
# return is not taken into account for breakpoint hit because we'd have a double-hit in this case
10411041
# (one for the line and the other for the return).
10421042

10431043
stop_info = {}
10441044
breakpoint = None
1045-
exist_result = False
10461045
stop = False
10471046
stop_reason = 111
10481047
bp_type = None
@@ -1057,33 +1056,25 @@ cdef class PyDBFrame:
10571056
breakpoint = breakpoints_for_file[line]
10581057
new_frame = frame
10591058
stop = True
1060-
if step_cmd in (108, 159) and (self._is_same_frame(stop_frame, frame) and is_line):
1061-
stop = False # we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
1059+
10621060
elif plugin_manager is not None and main_debugger.has_plugin_line_breaks:
10631061
result = plugin_manager.get_breakpoint(main_debugger, self, frame, event, self._args)
10641062
if result:
1065-
exist_result = True
1066-
flag, breakpoint, new_frame, bp_type = result
1063+
stop_on_plugin_breakpoint, breakpoint, new_frame, bp_type = result
10671064

10681065
if breakpoint:
10691066
# ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
10701067
# lets do the conditional stuff here
10711068
if breakpoint.expression is not None:
10721069
main_debugger.handle_breakpoint_expression(breakpoint, info, new_frame)
1073-
if breakpoint.is_logpoint and info.pydev_message is not None and len(info.pydev_message) > 0:
1074-
cmd = main_debugger.cmd_factory.make_io_message(info.pydev_message + os.linesep, '1')
1075-
main_debugger.writer.add_command(cmd)
10761070

1077-
if stop or exist_result:
1071+
if stop or stop_on_plugin_breakpoint:
10781072
eval_result = False
10791073
if breakpoint.has_condition:
10801074
eval_result = main_debugger.handle_breakpoint_condition(info, breakpoint, new_frame)
1081-
1082-
if breakpoint.has_condition:
10831075
if not eval_result:
10841076
stop = False
1085-
elif breakpoint.is_logpoint:
1086-
stop = False
1077+
stop_on_plugin_breakpoint = False
10871078

10881079
if is_call and (frame.f_code.co_name in ('<lambda>', '<module>') or (line == 1 and frame.f_code.co_name.startswith('<cell'))):
10891080
# If we find a call for a module, it means that the module is being imported/executed for the
@@ -1099,6 +1090,15 @@ cdef class PyDBFrame:
10991090

11001091
return self.trace_dispatch
11011092

1093+
# Handle logpoint (on a logpoint we should never stop).
1094+
if (stop or stop_on_plugin_breakpoint) and breakpoint.is_logpoint:
1095+
stop = False
1096+
stop_on_plugin_breakpoint = False
1097+
1098+
if info.pydev_message is not None and len(info.pydev_message) > 0:
1099+
cmd = main_debugger.cmd_factory.make_io_message(info.pydev_message + os.linesep, '1')
1100+
main_debugger.writer.add_command(cmd)
1101+
11021102
if main_debugger.show_return_values:
11031103
if is_return and (
11041104
(info.pydev_step_cmd in (108, 159, 128) and (self._is_same_frame(stop_frame, frame.f_back))) or
@@ -1125,7 +1125,7 @@ cdef class PyDBFrame:
11251125
suspend_other_threads=breakpoint and breakpoint.suspend_policy == "ALL",
11261126
)
11271127

1128-
elif flag and plugin_manager is not None:
1128+
elif stop_on_plugin_breakpoint and plugin_manager is not None:
11291129
result = plugin_manager.suspend(main_debugger, thread, frame, bp_type)
11301130
if result:
11311131
frame = result

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ def _is_same_frame(self, target_frame, current_frame):
557557
# cdef bint has_exception_breakpoints;
558558
# cdef bint can_skip;
559559
# cdef bint stop;
560+
# cdef bint stop_on_plugin_breakpoint;
560561
# cdef PyDBAdditionalThreadInfo info;
561562
# cdef int step_cmd;
562563
# cdef int line;
@@ -567,7 +568,6 @@ def _is_same_frame(self, target_frame, current_frame):
567568
# cdef dict breakpoints_for_file;
568569
# cdef dict stop_info;
569570
# cdef str curr_func_name;
570-
# cdef bint exist_result;
571571
# cdef dict frame_skips_cache;
572572
# cdef object frame_cache_key;
573573
# cdef tuple line_cache_key;
@@ -889,13 +889,12 @@ def trace_dispatch(self, frame, event, arg):
889889
# if DEBUG: print('NOT skipped: %s %s %s %s' % (frame.f_lineno, frame.f_code.co_name, event, frame.__class__.__name__))
890890

891891
try:
892-
flag = False
892+
stop_on_plugin_breakpoint = False
893893
# return is not taken into account for breakpoint hit because we'd have a double-hit in this case
894894
# (one for the line and the other for the return).
895895

896896
stop_info = {}
897897
breakpoint = None
898-
exist_result = False
899898
stop = False
900899
stop_reason = CMD_SET_BREAK
901900
bp_type = None
@@ -910,33 +909,25 @@ def trace_dispatch(self, frame, event, arg):
910909
breakpoint = breakpoints_for_file[line]
911910
new_frame = frame
912911
stop = True
913-
if step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE) and (self._is_same_frame(stop_frame, frame) and is_line):
914-
stop = False # we don't stop on breakpoint if we have to stop by step-over (it will be processed later)
912+
915913
elif plugin_manager is not None and main_debugger.has_plugin_line_breaks:
916914
result = plugin_manager.get_breakpoint(main_debugger, self, frame, event, self._args)
917915
if result:
918-
exist_result = True
919-
flag, breakpoint, new_frame, bp_type = result
916+
stop_on_plugin_breakpoint, breakpoint, new_frame, bp_type = result
920917

921918
if breakpoint:
922919
# ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
923920
# lets do the conditional stuff here
924921
if breakpoint.expression is not None:
925922
main_debugger.handle_breakpoint_expression(breakpoint, info, new_frame)
926-
if breakpoint.is_logpoint and info.pydev_message is not None and len(info.pydev_message) > 0:
927-
cmd = main_debugger.cmd_factory.make_io_message(info.pydev_message + os.linesep, '1')
928-
main_debugger.writer.add_command(cmd)
929923

930-
if stop or exist_result:
924+
if stop or stop_on_plugin_breakpoint:
931925
eval_result = False
932926
if breakpoint.has_condition:
933927
eval_result = main_debugger.handle_breakpoint_condition(info, breakpoint, new_frame)
934-
935-
if breakpoint.has_condition:
936928
if not eval_result:
937929
stop = False
938-
elif breakpoint.is_logpoint:
939-
stop = False
930+
stop_on_plugin_breakpoint = False
940931

941932
if is_call and (frame.f_code.co_name in ('<lambda>', '<module>') or (line == 1 and frame.f_code.co_name.startswith('<cell'))):
942933
# If we find a call for a module, it means that the module is being imported/executed for the
@@ -952,6 +943,15 @@ def trace_dispatch(self, frame, event, arg):
952943

953944
return self.trace_dispatch
954945

946+
# Handle logpoint (on a logpoint we should never stop).
947+
if (stop or stop_on_plugin_breakpoint) and breakpoint.is_logpoint:
948+
stop = False
949+
stop_on_plugin_breakpoint = False
950+
951+
if info.pydev_message is not None and len(info.pydev_message) > 0:
952+
cmd = main_debugger.cmd_factory.make_io_message(info.pydev_message + os.linesep, '1')
953+
main_debugger.writer.add_command(cmd)
954+
955955
if main_debugger.show_return_values:
956956
if is_return and (
957957
(info.pydev_step_cmd in (CMD_STEP_OVER, CMD_STEP_OVER_MY_CODE, CMD_SMART_STEP_INTO) and (self._is_same_frame(stop_frame, frame.f_back))) or
@@ -978,7 +978,7 @@ def trace_dispatch(self, frame, event, arg):
978978
suspend_other_threads=breakpoint and breakpoint.suspend_policy == "ALL",
979979
)
980980

981-
elif flag and plugin_manager is not None:
981+
elif stop_on_plugin_breakpoint and plugin_manager is not None:
982982
result = plugin_manager.suspend(main_debugger, thread, frame, bp_type)
983983
if result:
984984
frame = result

src/debugpy/_vendored/pydevd/tests_python/test_debugger.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4342,13 +4342,13 @@ def test_frame_eval_mode_corner_case_03(case_setup):
43424342
hit = writer.wait_for_breakpoint_hit(line=line + 1, reason=REASON_STEP_OVER)
43434343

43444344
writer.write_step_over(hit.thread_id) # i.e.: check that the jump target is still ok.
4345-
hit = writer.wait_for_breakpoint_hit(line=line, reason=REASON_STEP_OVER)
4345+
hit = writer.wait_for_breakpoint_hit(line=line, reason=REASON_STOP_ON_BREAKPOINT)
43464346

43474347
writer.write_step_over(hit.thread_id)
43484348
hit = writer.wait_for_breakpoint_hit(line=line + 1, reason=REASON_STEP_OVER)
43494349

43504350
writer.write_step_over(hit.thread_id)
4351-
hit = writer.wait_for_breakpoint_hit(line=line, reason=REASON_STEP_OVER)
4351+
hit = writer.wait_for_breakpoint_hit(line=line, reason=REASON_STOP_ON_BREAKPOINT)
43524352

43534353
writer.write_run_thread(hit.thread_id)
43544354

src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -521,38 +521,53 @@ def write_get_source(self, source_reference, success=True):
521521
return response
522522

523523

524-
def test_case_json_logpoints(case_setup_dap):
524+
@pytest.mark.parametrize('scenario', ['basic', 'condition', 'hitCondition'])
525+
def test_case_json_logpoints(case_setup_dap, scenario):
525526
with case_setup_dap.test_file('_debugger_case_change_breaks.py') as writer:
526527
json_facade = JsonFacade(writer)
527528

528529
json_facade.write_launch()
529530
break_2 = writer.get_line_index_with_content('break 2')
530531
break_3 = writer.get_line_index_with_content('break 3')
531-
json_facade.write_set_breakpoints(
532-
[break_2, break_3],
533-
line_to_info={
534-
break_2: {'log_message': 'var {repr("_a")} is {_a}'}
535-
})
532+
if scenario == 'basic':
533+
json_facade.write_set_breakpoints(
534+
[break_2, break_3],
535+
line_to_info={
536+
break_2: {'log_message': 'var {repr("_a")} is {_a}'}
537+
})
538+
elif scenario == 'condition':
539+
json_facade.write_set_breakpoints(
540+
[break_2, break_3],
541+
line_to_info={
542+
break_2: {'log_message': 'var {repr("_a")} is {_a}', 'condition': 'True'}
543+
})
544+
elif scenario == 'hitCondition':
545+
json_facade.write_set_breakpoints(
546+
[break_2, break_3],
547+
line_to_info={
548+
break_2: {'log_message': 'var {repr("_a")} is {_a}', 'hit_condition': '1'}
549+
})
536550
json_facade.write_make_initial_run()
537551

538552
# Should only print, not stop on logpoints.
539-
messages = []
540-
while True:
541-
output_event = json_facade.wait_for_json_message(OutputEvent)
553+
554+
# Just one hit at the end (break 3).
555+
json_facade.wait_for_thread_stopped(line=break_3)
556+
json_facade.write_continue()
557+
558+
def accept_message(output_event):
542559
msg = output_event.body.output
543560
ctx = output_event.body.category
544561

545562
if ctx == 'stdout':
546563
msg = msg.strip()
547-
if msg == "var '_a' is 2":
548-
messages.append(msg)
564+
return msg == "var '_a' is 2"
549565

550-
if len(messages) == 2:
551-
break
552-
553-
# Just one hit at the end (break 3).
554-
json_facade.wait_for_thread_stopped(line=break_3)
555-
json_facade.write_continue()
566+
messages = json_facade.mark_messages(OutputEvent, accept_message)
567+
if scenario == 'hitCondition':
568+
assert len(messages) == 1
569+
else:
570+
assert len(messages) == 2
556571

557572
writer.finished_ok = True
558573

tests/debugpy/test_breakpoints.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -205,18 +205,6 @@ def code_to_debug():
205205
},
206206
)
207207

208-
if condition:
209-
session.wait_for_stop(
210-
"breakpoint", expected_frames=[some.dap.frame(code_to_debug, line="bp")]
211-
)
212-
213-
var_i = session.get_variable("i")
214-
assert var_i == some.dict.containing(
215-
{"name": "i", "evaluateName": "i", "type": "int", "value": "5"}
216-
)
217-
218-
session.request_continue()
219-
220208
session.wait_for_stop(
221209
"breakpoint",
222210
expected_frames=[some.dap.frame(code_to_debug, line="wait_for_output")],
@@ -228,9 +216,12 @@ def code_to_debug():
228216
if "internalConsole" not in str(run):
229217
assert not session.captured_stdout()
230218

231-
expected_stdout = "".join(
232-
(r"{0}\r?\n".format(re.escape(str(i))) for i in range(0, 10))
233-
)
219+
if condition:
220+
expected_stdout = "5\r?\n"
221+
else:
222+
expected_stdout = "".join(
223+
(r"{0}\r?\n".format(re.escape(str(i))) for i in range(0, 10))
224+
)
234225
expected_stderr = "".join(
235226
(r"{0}\r?\n".format(re.escape(str(i * 10))) for i in range(0, 10))
236227
)

0 commit comments

Comments
 (0)