Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to sys.monitoring to get debugpy tests to pass #290

Merged
merged 38 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6da7d9f
Updates from debugpy to get debugpy tests to pass
rchiodo Sep 17, 2024
02c1825
Remove logging
rchiodo Sep 17, 2024
6a12743
Update repr tests
rchiodo Sep 18, 2024
dfa917e
Fix last test failure, update cython output
rchiodo Sep 18, 2024
9d57c97
Put back unchanged modules
rchiodo Sep 18, 2024
45bd5e9
Update dlls after pr build in debugpy
rchiodo Sep 23, 2024
d87ccff
Merge remote-tracking branch 'upstream/main' into rchiodo/debugpy_upd…
rchiodo Oct 7, 2024
46e6092
Regenerate cython
rchiodo Oct 7, 2024
895c855
Remove some of the unnecessary logging
rchiodo Oct 8, 2024
f39c6ca
Remove just_raised logic, was failing a test
rchiodo Oct 8, 2024
06edbb3
Fix django problem a different way
rchiodo Oct 8, 2024
202499c
Forgot jinja plugin
rchiodo Oct 8, 2024
c23b513
Add django test that matches failure case in debugpy
rchiodo Oct 8, 2024
3778455
Add tests to verify changes
rchiodo Oct 9, 2024
7f808d4
Fix test for 3.12 and add more restrictions on jump/line check
rchiodo Oct 10, 2024
597018b
Remove extra logging and special cases in tests for 3_12
rchiodo Oct 10, 2024
3c9f6fa
Put back version for ipython
rchiodo Oct 10, 2024
a906bcc
Fix cython bits
rchiodo Oct 10, 2024
2665bca
Remove unnecessary file
rchiodo Oct 10, 2024
00defa9
Turn cython back on
rchiodo Oct 10, 2024
e29f955
Remove some more unnecessary logging
rchiodo Oct 10, 2024
4e4ee8f
Fix default argument for should_stop_on_exception in Cython
rchiodo Oct 11, 2024
873321b
Get test like debugpy has
rchiodo Oct 11, 2024
75bdfdf
Switch test to attach only
rchiodo Oct 11, 2024
e13e2cb
Skip flakey test on 3_12
rchiodo Oct 11, 2024
83f9bc5
Skip new test on pypy
rchiodo Oct 11, 2024
0ee463b
Disabling the flakey test
rchiodo Oct 11, 2024
83d21c5
Review feedback and update logic for f_unhandled cache
rchiodo Oct 14, 2024
faecb6b
Fix string problem and generate test for it
rchiodo Oct 15, 2024
b9b2626
Remove extra logging
rchiodo Oct 15, 2024
2cdcb7d
Merge branch 'rchiodo/debugpy_updates' of https://github.com/rchiodo/…
rchiodo Oct 15, 2024
36e11f0
Update cython files
rchiodo Oct 15, 2024
d0e781e
Weird difference in print for new test?
rchiodo Oct 15, 2024
c484988
Add importlib-metadata as dependency of tests
rchiodo Oct 15, 2024
f933c81
Remove unnecessary variable and make test reflect what it's testing
rchiodo Oct 15, 2024
7590236
Update cython output
rchiodo Oct 15, 2024
872a0d5
Make new test retry
rchiodo Oct 15, 2024
ff3e742
Remove unnecessary get_file_type check
rchiodo Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pydevd-tests-python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ jobs:
pip install psutil --no-warn-script-location
pip install ipython --no-warn-script-location
pip install untangle --no-warn-script-location
pip install importlib-metadata --no-warn-script-location
- name: Install Python 3.x deps
if: contains(matrix.name, 'py3') && !contains(matrix.name, 'pypy') && !contains(matrix.name, 'py312') && !contains(matrix.name, 'py311')
run: |
Expand Down
3 changes: 2 additions & 1 deletion _pydevd_bundle/pydevd_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,10 @@ def _current_frames():
IS_PY310_OR_GREATER = sys.version_info >= (3, 10)
IS_PY311_OR_GREATER = sys.version_info >= (3, 11)
IS_PY312_OR_GREATER = sys.version_info >= (3, 12)
IS_PY313_OR_GREATER = sys.version_info >= (3, 13)

# Not currently supported in Python 3.12.
SUPPORT_ATTACH_TO_PID = not IS_PY312_OR_GREATER
SUPPORT_ATTACH_TO_PID = not IS_PY313_OR_GREATER


def version_str(v):
Expand Down
352 changes: 191 additions & 161 deletions _pydevd_bundle/pydevd_cython.c

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions _pydevd_bundle/pydevd_cython.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1374,12 +1374,12 @@ cdef class PyDBFrame:


# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
def should_stop_on_exception(py_db, PyDBAdditionalThreadInfo info, frame, thread, arg, prev_user_uncaught_exc_info):
def should_stop_on_exception(py_db, PyDBAdditionalThreadInfo info, frame, thread, arg, prev_user_uncaught_exc_info, is_unwind=False):
cdef bint should_stop;
cdef bint was_just_raised;
cdef list check_excs;
# ELSE
# def should_stop_on_exception(py_db, info, frame, thread, arg, prev_user_uncaught_exc_info):
# def should_stop_on_exception(py_db, info, frame, thread, arg, prev_user_uncaught_exc_info, is_unwind=False):
# ENDIF

should_stop = False
Expand All @@ -1396,7 +1396,7 @@ def should_stop_on_exception(py_db, PyDBAdditionalThreadInfo info, frame, thread
exception_breakpoint = None
try:
if py_db.plugin is not None:
result = py_db.plugin.exception_break(py_db, frame, thread, arg)
result = py_db.plugin.exception_break(py_db, frame, thread, arg, is_unwind)
if result:
should_stop, frame = result
except:
Expand Down
1 change: 1 addition & 0 deletions _pydevd_bundle/pydevd_dont_trace_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'inputhookpyglet.py',
'inputhookqt4.py',
'inputhookqt5.py',
'inputhookqt6.py',
'inputhooktk.py',
'inputhookwx.py',
'matplotlibtools.py',
Expand Down
6 changes: 3 additions & 3 deletions _pydevd_bundle/pydevd_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -1053,12 +1053,12 @@ def trace_dispatch(self, frame, event, arg):


# IFDEF CYTHON
# def should_stop_on_exception(py_db, PyDBAdditionalThreadInfo info, frame, thread, arg, prev_user_uncaught_exc_info):
# def should_stop_on_exception(py_db, PyDBAdditionalThreadInfo info, frame, thread, arg, prev_user_uncaught_exc_info, is_unwind=False):
# cdef bint should_stop;
# cdef bint was_just_raised;
# cdef list check_excs;
# ELSE
def should_stop_on_exception(py_db, info, frame, thread, arg, prev_user_uncaught_exc_info):
def should_stop_on_exception(py_db, info, frame, thread, arg, prev_user_uncaught_exc_info, is_unwind=False):
# ENDIF

should_stop = False
Expand All @@ -1075,7 +1075,7 @@ def should_stop_on_exception(py_db, info, frame, thread, arg, prev_user_uncaught
exception_breakpoint = None
try:
if py_db.plugin is not None:
result = py_db.plugin.exception_break(py_db, frame, thread, arg)
result = py_db.plugin.exception_break(py_db, frame, thread, arg, is_unwind)
if result:
should_stop, frame = result
except:
Expand Down
25 changes: 25 additions & 0 deletions _pydevd_bundle/pydevd_frame_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from _pydev_bundle import pydev_log
import itertools
from typing import Any, Dict
from os.path import basename, splitext


class Frame(object):
Expand Down Expand Up @@ -42,8 +43,32 @@ def remove_exception_from_frame(frame):
def just_raised(trace):
if trace is None:
return False

return trace.tb_next is None

def short_tb(exc_tb):
traceback = []
while exc_tb:
traceback.append('{%r, %r, %r}' % (exc_tb.tb_frame.f_code.co_filename,
exc_tb.tb_frame.f_code.co_name,
exc_tb.tb_lineno))
exc_tb = exc_tb.tb_next
return 'Traceback: %s\n' % (' -> '.join(traceback))

def short_frame(frame):
if frame is None:
return 'None'

filename = frame.f_code.co_filename
name = splitext(basename(filename))[0]
return '%s::%s %s' % (name, frame.f_code.co_name, frame.f_lineno)

def short_stack(frame):
stack = []
while frame:
stack.append(short_frame(frame))
frame = frame.f_back
return 'Stack: %s\n' % (' -> '.join(stack))

def ignore_exception_trace(trace):
while trace is not None:
Expand Down
4 changes: 2 additions & 2 deletions _pydevd_bundle/pydevd_plugin_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ def suspend(self, py_db, thread, frame, bp_type):

return None

def exception_break(self, py_db, frame, thread, arg):
def exception_break(self, py_db, frame, thread, arg, is_unwind=False):
for plugin in self.active_plugins:
ret = plugin.exception_break(py_db, frame, thread, arg)
ret = plugin.exception_break(py_db, frame, thread, arg, is_unwind)
if ret is not None:
return ret

Expand Down
6 changes: 3 additions & 3 deletions _pydevd_bundle/pydevd_safe_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class SafeRepr(object):
# most level, and truncated to maxstring_inner characters inside
# collections.
maxstring_outer = 2**16
maxstring_inner = 30
maxstring_inner = 128
string_types = (str, bytes)
bytes = bytes
set_info = (set, "{", "}", False)
Expand All @@ -30,7 +30,7 @@ class SafeRepr(object):

# Collection types are recursively iterated for each limit in
# maxcollection.
maxcollection = (15, 10)
maxcollection = (60, 20)

# Specifies type, prefix string, suffix string, and whether to include a
# comma if there is only one element. (Using a sequence rather than a
Expand Down Expand Up @@ -61,7 +61,7 @@ class SafeRepr(object):
# All other types are treated identically to strings, but using
# different limits.
maxother_outer = 2**16
maxother_inner = 30
maxother_inner = 128

convert_to_hex = False
raw_value = False
Expand Down
89 changes: 74 additions & 15 deletions _pydevd_sys_monitoring/_pydevd_sys_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
from _pydevd_bundle.pydevd_trace_dispatch import is_unhandled_exception
from _pydevd_bundle.pydevd_breakpoints import stop_on_unhandled_exception
from _pydevd_bundle.pydevd_utils import get_clsname_for_code
from _pydevd_bundle.pydevd_dont_trace_files import PYDEV_FILE


# fmt: off
# IFDEF CYTHON
Expand Down Expand Up @@ -168,7 +170,6 @@ def _get_bootstrap_frame(depth: int) -> Tuple[Optional[FrameType], bool]:

return f_bootstrap, is_bootstrap_frame_internal


# fmt: off
# IFDEF CYTHON
# cdef _get_unhandled_exception_frame(int depth):
Expand All @@ -177,10 +178,19 @@ def _get_unhandled_exception_frame(depth: int) -> Optional[FrameType]:
# ENDIF
# fmt: on
try:
return _thread_local_info.f_unhandled
result = _thread_local_info.f_unhandled

# Make sure the result is from the same exception. That means the result is in the stack somewhere.
if result is not None:
orig = frame = _getframe(depth)
while result != frame and frame is not None:
frame = frame.f_back
if frame is not None:
return result
del _thread_local_info.f_unhandled
raise AttributeError("Unhandled frame from different exception")
except:
frame = _getframe(depth)
f_unhandled = frame
f_unhandled = _getframe(depth)

while f_unhandled is not None and f_unhandled.f_back is not None:
f_back = f_unhandled.f_back
Expand All @@ -205,6 +215,14 @@ def _get_unhandled_exception_frame(depth: int) -> Optional[FrameType]:
if f_back.f_code.co_name.startswith(("run", "_run")):
break

elif name == "<frozen runpy>":
if f_back.f_code.co_name.startswith(("run", "_run")):
break

elif name == "runpy":
if f_back.f_code.co_name.startswith(("run", "_run")):
break

f_unhandled = f_back

if f_unhandled is not None:
Expand Down Expand Up @@ -631,6 +649,7 @@ def _enable_line_tracing(code):
# ENDIF
# fmt: on
# print('enable line tracing', code)
_ensure_monitoring()
events = monitor.get_local_events(DEBUGGER_ID, code)
monitor.set_local_events(DEBUGGER_ID, code, events | monitor.events.LINE | monitor.events.JUMP)

Expand All @@ -643,6 +662,7 @@ def _enable_return_tracing(code):
# ENDIF
# fmt: on
# print('enable return tracing', code)
_ensure_monitoring()
events = monitor.get_local_events(DEBUGGER_ID, code)
monitor.set_local_events(DEBUGGER_ID, code, events | monitor.events.PY_RETURN)

Expand All @@ -654,6 +674,7 @@ def _enable_return_tracing(code):
def disable_code_tracing(code):
# ENDIF
# fmt: on
_ensure_monitoring()
monitor.set_local_events(DEBUGGER_ID, code, 0)


Expand Down Expand Up @@ -810,6 +831,7 @@ def _unwind_event(code, instruction, exc):
if thread_info is None:
return


py_db: object = GlobalDebuggerHolder.global_dbg
if py_db is None or py_db.pydb_disposed:
return
Expand All @@ -831,9 +853,10 @@ def _unwind_event(code, instruction, exc):
py_db.break_on_caught_exceptions or py_db.break_on_user_uncaught_exceptions or py_db.has_plugin_exception_breaks
)


if has_caught_exception_breakpoint_in_pydb:
_should_stop, frame, user_uncaught_exc_info = should_stop_on_exception(
py_db, thread_info.additional_info, frame, thread_info.thread, arg, None
py_db, thread_info.additional_info, frame, thread_info.thread, arg, None, is_unwind=True
)
if user_uncaught_exc_info:
# TODO: Check: this may no longer be needed as in the unwind we know it's
Expand All @@ -842,16 +865,17 @@ def _unwind_event(code, instruction, exc):
container_obj = _TryExceptContainerObj(py_db.collect_try_except_info(frame.f_code))
func_code_info.try_except_container_obj = container_obj

if is_unhandled_exception(
is_unhandled = is_unhandled_exception(
func_code_info.try_except_container_obj, py_db, frame, user_uncaught_exc_info[1], user_uncaught_exc_info[2]
):
# print('stop in user uncaught')
)

if is_unhandled:
handle_exception(py_db, thread_info.thread, frame, user_uncaught_exc_info[0], EXCEPTION_TYPE_USER_UNHANDLED)
return

break_on_uncaught_exceptions = py_db.break_on_uncaught_exceptions
if break_on_uncaught_exceptions:
if frame is _get_unhandled_exception_frame(depth=1):
if frame is _get_unhandled_exception_frame(1):
stop_on_unhandled_exception(py_db, thread_info.thread, thread_info.additional_info, arg)
return

Expand Down Expand Up @@ -881,7 +905,7 @@ def _raise_event(code, instruction, exc):
thread_info = _get_thread_info(True, 1)
if thread_info is None:
return

py_db: object = GlobalDebuggerHolder.global_dbg
if py_db is None or py_db.pydb_disposed:
return
Expand All @@ -895,17 +919,22 @@ def _raise_event(code, instruction, exc):
if func_code_info.always_skip_code:
return

# print('_raise_event --- ', code, exc)

frame = _getframe(1)
arg = (type(exc), exc, exc.__traceback__)

# Compute the previous exception info (if any). We use it to check if the exception
# should be stopped
prev_exc_info = _thread_local_info._user_uncaught_exc_info if hasattr(_thread_local_info, "_user_uncaught_exc_info") else None
should_stop, frame, _user_uncaught_exc_info = should_stop_on_exception(
py_db, thread_info.additional_info, frame, thread_info.thread, arg, None
py_db, thread_info.additional_info, frame, thread_info.thread, arg, prev_exc_info
)

# Save the current exception info for the next raise event.
_thread_local_info._user_uncaught_exc_info = _user_uncaught_exc_info

# print('!!!! should_stop (in raise)', should_stop)
if should_stop:
handle_exception(py_db, thread_info.thread, frame, arg, EXCEPTION_TYPE_HANDLED)
return


# fmt: off
Expand Down Expand Up @@ -1307,6 +1336,10 @@ def _jump_event(code, from_offset, to_offset):
if py_db is None or py_db.pydb_disposed:
return monitor.DISABLE

# If we get another jump event, remove the extra check for the line event
if hasattr(_thread_local_info, "f_disable_next_line_if_match"):
del _thread_local_info.f_disable_next_line_if_match

if not thread_info.trace or thread_info.thread._is_stopped:
# For thread-related stuff we can't disable the code tracing because other
# threads may still want it...
Expand All @@ -1325,14 +1358,17 @@ def _jump_event(code, from_offset, to_offset):

from_line = func_code_info.get_line_of_offset(from_offset)
to_line = func_code_info.get_line_of_offset(to_offset)
# print('jump event', code.co_name, 'from line', from_line, 'to line', to_line)

if from_line != to_line:
# I.e.: use case: "yield from [j for j in a if j % 2 == 0]"
return monitor.DISABLE

# We know the frame depth.
frame = _getframe(1)

# Disable the next line event as we're jumping to a line. The line event will be redundant.
_thread_local_info.f_disable_next_line_if_match = (func_code_info.co_filename, frame.f_lineno)

return _internal_line_event(func_code_info, frame, frame.f_lineno)


Expand Down Expand Up @@ -1360,6 +1396,15 @@ def _line_event(code, line):
if py_db is None or py_db.pydb_disposed:
return monitor.DISABLE

# If we get another line event, remove the extra check for the line event
if hasattr(_thread_local_info, "f_disable_next_line_if_match"):
(co_filename, line_to_skip) = _thread_local_info.f_disable_next_line_if_match
del _thread_local_info.f_disable_next_line_if_match
if line_to_skip is line and co_filename == code.co_filename:
# The last jump already jumped to this line and we haven't had any
# line events or jumps since then. We don't want to consider this line twice
return

if not thread_info.trace or thread_info.thread._is_stopped:
# For thread-related stuff we can't disable the code tracing because other
# threads may still want it...
Expand Down Expand Up @@ -1604,6 +1649,7 @@ def _start_method_event(code, instruction_offset):
# threads may still want it...
return


frame = _getframe(1)
func_code_info = _get_func_code_info(code, frame)
if func_code_info.always_skip_code:
Expand Down Expand Up @@ -1651,6 +1697,19 @@ def _start_method_event(code, instruction_offset):

return monitor.DISABLE

# fmt: off
# IFDEF CYTHON
# cpdef _ensure_monitoring():
# ELSE
def _ensure_monitoring():
# ENDIF
# fmt: on
DEBUGGER_ID = monitor.DEBUGGER_ID
if not monitor.get_tool(DEBUGGER_ID):
monitor.use_tool_id(DEBUGGER_ID, "pydevd")
update_monitor_events()
restart_events()


# fmt: off
# IFDEF CYTHON
Expand Down
Loading
Loading