Updates from PyDev: workaround for debugger on IronPython

This commit is contained in:
Elizaveta Shashkova
2017-10-19 20:22:50 +03:00
parent bfc1d9cec4
commit 9f1870077a
6 changed files with 90 additions and 39 deletions
@@ -1,5 +1,5 @@
import sys
from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON
from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON, IS_IRONPYTHON
# IFDEF CYTHON
# ELSE
from _pydevd_bundle.pydevd_frame import PyDBFrame
@@ -37,6 +37,14 @@ if not hasattr(sys, '_current_frames'):
ret[thread.getId()] = frame
return ret
if IS_IRONPYTHON:
_tid_to_last_frame = {}
# IronPython doesn't have it. Let's use our workaround...
def _current_frames():
return _tid_to_last_frame
else:
raise RuntimeError('Unable to proceed (sys._current_frames not available in this Python implementation).')
else:
@@ -110,14 +118,6 @@ class PyDBAdditionalThreadInfo(object):
return [v]
return []
# IFDEF CYTHON
# def create_db_frame(self, *args, **kwargs):
# raise AssertionError('This method should not be called on cython (PyDbFrame should be used directly).')
# ELSE
# just create the db frame directly
create_db_frame = PyDBFrame
# ENDIF
def __str__(self):
return 'State:%s Stop:%s Cmd: %s Kill:%s' % (
self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill)
@@ -38,6 +38,7 @@ import os
from _pydevd_bundle import pydevd_vm_type
IS_JYTHON = pydevd_vm_type.get_vm_type() == pydevd_vm_type.PydevdVmType.JYTHON
IS_IRONPYTHON = sys.platform == 'cli'
IS_JYTH_LESS25 = False
if IS_JYTHON:
@@ -3,7 +3,7 @@
# DO NOT edit manually!
# DO NOT edit manually!
import sys
from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON
from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON, IS_IRONPYTHON
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
# ELSE
# from _pydevd_bundle.pydevd_frame import PyDBFrame
@@ -41,6 +41,14 @@ if not hasattr(sys, '_current_frames'):
ret[thread.getId()] = frame
return ret
if IS_IRONPYTHON:
_tid_to_last_frame = {}
# IronPython doesn't have it. Let's use our workaround...
def _current_frames():
return _tid_to_last_frame
else:
raise RuntimeError('Unable to proceed (sys._current_frames not available in this Python implementation).')
else:
@@ -114,14 +122,6 @@ cdef class PyDBAdditionalThreadInfo:
return [v]
return []
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
def create_db_frame(self, *args, **kwargs):
raise AssertionError('This method should not be called on cython (PyDbFrame should be used directly).')
# ELSE
# # just create the db frame directly
# create_db_frame = PyDBFrame
# ENDIF
def __str__(self):
return 'State:%s Stop:%s Cmd: %s Kill:%s' % (
self.pydev_state, self.pydev_step_stop, self.pydev_step_cmd, self.pydev_notify_kill)
@@ -898,7 +898,7 @@ import traceback
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
from _pydev_imps._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_constants import get_thread_id
from _pydevd_bundle.pydevd_constants import get_thread_id, IS_IRONPYTHON
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE
from _pydevd_bundle.pydevd_kill_all_pydevd_threads import kill_all_pydev_threads
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER
@@ -1093,3 +1093,23 @@ cdef class ThreadTracer:
# (https://github.com/fabioz/PyDev.Debugger/issues/8)
pass
return None
if IS_IRONPYTHON:
# This is far from ideal, as we'll leak frames (we'll always have the last created frame, not really
# the last topmost frame saved -- this should be Ok for our usage, but it may leak frames and things
# may live longer... as IronPython is garbage-collected, things should live longer anyways, so, it
# shouldn't be an issue as big as it's in CPython -- it may still be annoying, but this should
# be a reasonable workaround until IronPython itself is able to provide that functionality).
#
# See: https://github.com/IronLanguages/main/issues/1630
from _pydevd_bundle.pydevd_additional_thread_info_regular import _tid_to_last_frame
_original_call = ThreadTracer.__call__
def __call__(self, frame, event, arg):
_tid_to_last_frame[self._args[1].ident] = frame
return _original_call(self, frame, event, arg)
ThreadTracer.__call__ = __call__
@@ -2,7 +2,7 @@ import traceback
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
from _pydev_imps._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_constants import get_thread_id
from _pydevd_bundle.pydevd_constants import get_thread_id, IS_IRONPYTHON
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE
from _pydevd_bundle.pydevd_kill_all_pydevd_threads import kill_all_pydev_threads
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame, NORM_PATHS_AND_BASE_CONTAINER
@@ -14,8 +14,15 @@ from _pydevd_bundle.pydevd_tracing import SetTrace
# ELSE
from _pydevd_bundle.pydevd_additional_thread_info import PyDBAdditionalThreadInfo
from _pydevd_bundle.pydevd_frame import PyDBFrame
# ENDIF
try:
from _pydevd_bundle.pydevd_signature import send_signature_call_trace
except ImportError:
def send_signature_call_trace(*args, **kwargs):
pass
threadingCurrentThread = threading.currentThread
get_file_type = DONT_TRACE.get
@@ -33,6 +40,7 @@ get_file_type = DONT_TRACE.get
global_cache_skips = {}
global_cache_frame_skips = {}
def trace_dispatch(py_db, frame, event, arg):
t = threadingCurrentThread()
@@ -47,13 +55,14 @@ def trace_dispatch(py_db, frame, event, arg):
additional_info = t.additional_info = PyDBAdditionalThreadInfo()
thread_tracer = ThreadTracer((py_db, t, additional_info, global_cache_skips, global_cache_frame_skips))
# IFDEF CYTHON
# t._tracer = thread_tracer # Hack for cython to keep it alive while the thread is alive (just the method in the SetTrace is not enough).
# ELSE
# ENDIF
# IFDEF CYTHON
# t._tracer = thread_tracer # Hack for cython to keep it alive while the thread is alive (just the method in the SetTrace is not enough).
# ELSE
# ENDIF
SetTrace(thread_tracer.__call__)
return thread_tracer.__call__(frame, event, arg)
# IFDEF CYTHON
# cdef class SafeCallWrapper:
# cdef method_object
@@ -75,8 +84,8 @@ def trace_dispatch(py_db, frame, event, arg):
class ThreadTracer:
def __init__(self, args):
self._args = args
# ENDIF
# ENDIF
def __call__(self, frame, event, arg):
''' This is the callback used when we enter some context in the debugger.
@@ -109,7 +118,7 @@ class ThreadTracer:
try:
if py_db._finish_debugging_session:
if not py_db._termination_event_set:
#that was not working very well because jython gave some socket errors
# that was not working very well because jython gave some socket errors
try:
if py_db.output_checker is None:
kill_all_pydev_threads()
@@ -143,10 +152,11 @@ class ThreadTracer:
# print('skipped: trace_dispatch (cache hit)', cache_key, frame.f_lineno, event, frame.f_code.co_name)
return None
file_type = get_file_type(abs_path_real_path_and_base[-1]) #we don't want to debug threading or anything related to pydevd
file_type = get_file_type(
abs_path_real_path_and_base[-1]) # we don't want to debug threading or anything related to pydevd
if file_type is not None:
if file_type == 1: # inlining LIB_FILE = 1
if file_type == 1: # inlining LIB_FILE = 1
if py_db.not_in_scope(filename):
# print('skipped: trace_dispatch (not in scope)', abs_path_real_path_and_base[-1], frame.f_lineno, event, frame.f_code.co_name, file_type)
cache_skips[cache_key] = 1
@@ -166,11 +176,13 @@ class ThreadTracer:
# print('trace_dispatch', base, frame.f_lineno, event, frame.f_code.co_name, file_type)
if additional_info.is_tracing:
return None #we don't wan't to trace code invoked from pydevd_frame.trace_dispatch
return None # we don't wan't to trace code invoked from pydevd_frame.trace_dispatch
# Just create PyDBFrame directly (removed support for Python versions < 2.5, which required keeping a weak
# reference to the frame).
ret = PyDBFrame((py_db, filename, additional_info, t, frame_skips_cache, (frame.f_code.co_name, frame.f_code.co_firstlineno, filename))).trace_dispatch(frame, event, arg)
ret = PyDBFrame((py_db, filename, additional_info, t, frame_skips_cache,
(frame.f_code.co_name, frame.f_code.co_firstlineno, filename))).trace_dispatch(frame,
event, arg)
if ret is None:
cache_skips[cache_key] = 1
return None
@@ -186,7 +198,7 @@ class ThreadTracer:
except Exception:
if py_db._finish_debugging_session:
return None # Don't log errors when we're shutting down.
return None # Don't log errors when we're shutting down.
# Log it
try:
if traceback is not None:
@@ -197,3 +209,25 @@ class ThreadTracer:
# (https://github.com/fabioz/PyDev.Debugger/issues/8)
pass
return None
if IS_IRONPYTHON:
# This is far from ideal, as we'll leak frames (we'll always have the last created frame, not really
# the last topmost frame saved -- this should be Ok for our usage, but it may leak frames and things
# may live longer... as IronPython is garbage-collected, things should live longer anyways, so, it
# shouldn't be an issue as big as it's in CPython -- it may still be annoying, but this should
# be a reasonable workaround until IronPython itself is able to provide that functionality).
#
# See: https://github.com/IronLanguages/main/issues/1630
from _pydevd_bundle.pydevd_additional_thread_info_regular import _tid_to_last_frame
_original_call = ThreadTracer.__call__
def __call__(self, frame, event, arg):
_tid_to_last_frame[self._args[1].ident] = frame
return _original_call(self, frame, event, arg)
ThreadTracer.__call__ = __call__
@@ -24,9 +24,9 @@ class C:
return 'unchanged'
"""
IS_JYTHON = sys.platform.find('java') != -1
from _pydevd_bundle.pydevd_constants import IS_JYTHON, IS_IRONPYTHON
@pytest.mark.skipif(IS_JYTHON, reason='CPython related test')
@pytest.mark.skipif(IS_JYTHON or IS_IRONPYTHON, reason='CPython related test')
class Test(unittest.TestCase):
@@ -3,7 +3,7 @@ import sys
import unittest
from _pydevd_bundle.pydevd_save_locals import save_locals
from _pydevd_bundle.pydevd_constants import IS_JYTHON
from _pydevd_bundle.pydevd_constants import IS_JYTHON, IS_IRONPYTHON
import pytest
@@ -31,18 +31,17 @@ def check_method(fn):
@pytest.mark.skipif(IS_JYTHON or IS_IRONPYTHON, reason='CPython/pypy only')
class TestSetLocals(unittest.TestCase):
"""
Test setting locals in one function from another function using several approaches.
"""
@pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only')
def test_set_locals_using_save_locals(self):
x = check_method(use_save_locals)
self.assertEqual(x, 2) # Expected to succeed
@pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only')
def test_frame_simple_change(self):
frame = sys._getframe()
a = 20
@@ -51,7 +50,6 @@ class TestSetLocals(unittest.TestCase):
self.assertEquals(50, a)
@pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only')
def test_frame_co_freevars(self):
outer_var = 20
@@ -64,7 +62,6 @@ class TestSetLocals(unittest.TestCase):
func()
@pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only')
def test_frame_co_cellvars(self):
def check_co_vars(a):
@@ -81,7 +78,6 @@ class TestSetLocals(unittest.TestCase):
check_co_vars(1)
@pytest.mark.skipif(IS_JYTHON, reason='CPython/pypy only')
def test_frame_change_in_inner_frame(self):
def change(f):
self.assert_(f is not sys._getframe())