mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 11:53:49 +07:00
Remove pydev warning if old trace function equals to new one. Add argparse for `docrunner.py`. Add tests for `doctest` with parameters. Add debugger tests for `doctest` with parameters. Merge-request: IJ-MR-111959 Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com> GitOrigin-RevId: 608080b3b5db93718f62a3dddd17893fe8118539
265 lines
10 KiB
Python
265 lines
10 KiB
Python
import ctypes
|
|
import os
|
|
from _pydev_bundle import pydev_log, pydev_monkey
|
|
from _pydevd_bundle.pydevd_constants import get_frame, IS_PY2, IS_PY37_OR_GREATER, IS_CPYTHON, IS_WINDOWS, IS_LINUX, IS_MACOS, \
|
|
IS_64BIT_PROCESS, IS_AARCH64, IS_PYCHARM_ATTACH
|
|
from _pydev_imps._pydev_saved_modules import thread, threading
|
|
|
|
try:
|
|
import cStringIO as StringIO #may not always be available @UnusedImport
|
|
except:
|
|
try:
|
|
import StringIO #@Reimport
|
|
except:
|
|
import io as StringIO
|
|
|
|
|
|
import sys #@Reimport
|
|
import traceback
|
|
|
|
_original_settrace = sys.settrace
|
|
|
|
class TracingFunctionHolder:
|
|
'''This class exists just to keep some variables (so that we don't keep them in the global namespace).
|
|
'''
|
|
_original_tracing = None
|
|
_warn = True
|
|
_lock = thread.allocate_lock()
|
|
_traceback_limit = 1
|
|
_warnings_shown = {}
|
|
|
|
|
|
def get_exception_traceback_str():
|
|
exc_info = sys.exc_info()
|
|
s = StringIO.StringIO()
|
|
traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], file=s)
|
|
return s.getvalue()
|
|
|
|
def _get_stack_str(frame):
|
|
|
|
msg = '\nIf this is needed, please check: ' + \
|
|
'\nhttp://pydev.blogspot.com/2007/06/why-cant-pydev-debugger-work-with.html' + \
|
|
'\nto see how to restore the debug tracing back correctly.\n'
|
|
|
|
if TracingFunctionHolder._traceback_limit:
|
|
s = StringIO.StringIO()
|
|
s.write('Call Location:\n')
|
|
traceback.print_stack(f=frame, limit=TracingFunctionHolder._traceback_limit, file=s)
|
|
msg = msg + s.getvalue()
|
|
|
|
return msg
|
|
|
|
def _internal_set_trace(tracing_func):
|
|
if TracingFunctionHolder._warn and sys.gettrace() != tracing_func:
|
|
frame = get_frame()
|
|
if frame is not None and frame.f_back is not None:
|
|
filename = frame.f_back.f_code.co_filename.lower()
|
|
if not filename.endswith('threading.py') and not filename.endswith('pydevd_tracing.py'):
|
|
message = \
|
|
'\nPYDEV DEBUGGER WARNING:' + \
|
|
'\nsys.settrace() should not be used when the debugger is being used.' + \
|
|
'\nThis may cause the debugger to stop working correctly.' + \
|
|
'%s' % _get_stack_str(frame.f_back)
|
|
|
|
if message not in TracingFunctionHolder._warnings_shown:
|
|
#only warn about each message once...
|
|
TracingFunctionHolder._warnings_shown[message] = 1
|
|
sys.stderr.write('%s\n' % (message,))
|
|
sys.stderr.flush()
|
|
|
|
if TracingFunctionHolder._original_tracing:
|
|
TracingFunctionHolder._original_tracing(tracing_func)
|
|
|
|
|
|
def SetTrace(tracing_func):
|
|
if TracingFunctionHolder._original_tracing is None:
|
|
# This may happen before replace_sys_set_trace_func is called.
|
|
sys.settrace(tracing_func)
|
|
return
|
|
|
|
try:
|
|
TracingFunctionHolder._lock.acquire()
|
|
TracingFunctionHolder._warn = False
|
|
_internal_set_trace(tracing_func)
|
|
TracingFunctionHolder._warn = True
|
|
finally:
|
|
TracingFunctionHolder._lock.release()
|
|
|
|
|
|
def replace_sys_set_trace_func():
|
|
if TracingFunctionHolder._original_tracing is None:
|
|
TracingFunctionHolder._original_tracing = sys.settrace
|
|
sys.settrace = _internal_set_trace
|
|
|
|
|
|
def restore_sys_set_trace_func():
|
|
if TracingFunctionHolder._original_tracing is not None:
|
|
sys.settrace = TracingFunctionHolder._original_tracing
|
|
TracingFunctionHolder._original_tracing = None
|
|
|
|
|
|
def load_python_helper_lib():
|
|
if not IS_CPYTHON:
|
|
pydev_log.info('Helper lib to set tracing to all threads not loaded: unsupported Python implementation')
|
|
return None
|
|
|
|
if ctypes is None:
|
|
pydev_log.info('Helper lib to set tracing to all threads not loaded: ctypes not found')
|
|
return None
|
|
|
|
if sys.version_info[:2] > (3, 11):
|
|
pydev_log.info('Helper lib to set tracing to all threads not loaded: unsupported Python version')
|
|
return None
|
|
|
|
if IS_WINDOWS:
|
|
if IS_64BIT_PROCESS:
|
|
suffix = 'amd64'
|
|
else:
|
|
suffix = 'x86'
|
|
|
|
filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_%s.dll' % (suffix,))
|
|
|
|
elif IS_LINUX:
|
|
if IS_64BIT_PROCESS:
|
|
if IS_AARCH64:
|
|
suffix = 'aarch64'
|
|
else:
|
|
suffix = 'amd64'
|
|
else:
|
|
suffix = 'x86'
|
|
|
|
filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach_linux_%s.so' % (suffix,))
|
|
|
|
elif IS_MACOS:
|
|
filename = os.path.join(os.path.dirname(__file__), 'pydevd_attach_to_process', 'attach.dylib')
|
|
|
|
else:
|
|
pydev_log.info('Unable to set trace to all threads in platform: %s' % sys.platform)
|
|
return None
|
|
|
|
if not os.path.exists(filename):
|
|
pydev_log.error('Expected: %s to exist.' % filename)
|
|
return None
|
|
|
|
try:
|
|
# Load as pydll so that we don't release the gil.
|
|
lib = ctypes.pydll.LoadLibrary(filename)
|
|
return lib
|
|
except:
|
|
# Only show message if tracing is on (we don't have pre-compiled
|
|
# binaries for all architectures -- i.e.: ARM).
|
|
pydev_log.error('Error loading: %s' % filename)
|
|
return None
|
|
|
|
|
|
def set_trace_to_threads(tracing_func):
|
|
lib = load_python_helper_lib()
|
|
if lib is None: # This is the case if it's not CPython.
|
|
return -1
|
|
|
|
if hasattr(sys, 'getswitchinterval'):
|
|
get_interval, set_interval = sys.getswitchinterval, sys.setswitchinterval
|
|
else:
|
|
get_interval, set_interval = sys.getcheckinterval, sys.setcheckinterval
|
|
|
|
prev_value = get_interval()
|
|
ret = 0
|
|
try:
|
|
if not IS_PY37_OR_GREATER:
|
|
# Prevent going to any other thread... if we switch the thread during this operation we
|
|
# could potentially corrupt the interpreter.
|
|
# Note: on CPython 3.7 onwards this is not needed (we have a different implementation
|
|
# for setting the tracing for other threads in this case).
|
|
set_interval(2 ** 15)
|
|
|
|
set_trace_func = TracingFunctionHolder._original_tracing or sys.settrace
|
|
|
|
# Note: use sys._current_frames() keys to get the thread ids because it'll return
|
|
# thread ids created in C/C++ where there's user code running, unlike the APIs
|
|
# from the threading module which see only threads created through it (unless
|
|
# a call for threading.current_thread() was previously done in that thread,
|
|
# in which case a dummy thread would've been created for it).
|
|
thread_idents = set(sys._current_frames().keys())
|
|
|
|
if IS_WINDOWS and IS_PYCHARM_ATTACH:
|
|
# On Windows, when attaching to a process, some existing threads may not
|
|
# appear in sys._current_frames()` but be available through the `threading`
|
|
# facilities. See: PY-44778.
|
|
thread_idents = thread_idents.union(
|
|
set(t.ident for t in threading.enumerate()))
|
|
|
|
thread_idents = thread_idents.difference(
|
|
# Ignore pydevd threads.
|
|
set(t.ident for t in threading.enumerate() if getattr(t, 'pydev_do_not_trace', False))
|
|
)
|
|
|
|
curr_ident = thread.get_ident()
|
|
curr_thread = threading._active.get(curr_ident)
|
|
|
|
for thread_ident in thread_idents:
|
|
# If that thread is not available in the threading module we also need to create a
|
|
# dummy thread for it (otherwise it'll be invisible to the debugger).
|
|
if thread_ident not in threading._active:
|
|
|
|
class _DummyThread(threading._DummyThread):
|
|
|
|
def _set_ident(self):
|
|
# Note: Hack to set the thread ident that we want.
|
|
if IS_PY2:
|
|
self._Thread__ident = thread_ident
|
|
else:
|
|
self._ident = thread_ident
|
|
|
|
t = _DummyThread()
|
|
# Reset to the base class (don't expose our own version of the class).
|
|
t.__class__ = threading._DummyThread
|
|
|
|
with threading._active_limbo_lock:
|
|
# On Py2 it'll put in active getting the current indent, not using the
|
|
# ident that was set, so, we have to update it (should be harmless on Py3
|
|
# so, do it always).
|
|
threading._active[thread_ident] = t
|
|
threading._active[curr_ident] = curr_thread
|
|
|
|
if t.ident != thread_ident:
|
|
# Check if it actually worked.
|
|
pydev_log.error('pydevd: creation of _DummyThread with fixed thread ident did not succeed.')
|
|
|
|
# Some (ptvsd) tests failed because of this, so, leave it always disabled for now.
|
|
# show_debug_info = 1 if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1 else 0
|
|
show_debug_info = 0
|
|
|
|
if IS_PY37_OR_GREATER:
|
|
# Hack to increase _Py_TracingPossible.
|
|
# See comments on py_settrace_37.hpp
|
|
proceed = thread.allocate_lock()
|
|
proceed.acquire()
|
|
|
|
def dummy_trace_on_py37(frame, event, arg):
|
|
return dummy_trace_on_py37
|
|
|
|
def increase_tracing_count_on_py37():
|
|
SetTrace(dummy_trace_on_py37)
|
|
proceed.release()
|
|
|
|
start_new_thread = pydev_monkey.get_original_start_new_thread(thread)
|
|
start_new_thread(increase_tracing_count_on_py37, ())
|
|
proceed.acquire() # Only proceed after the release() is done.
|
|
proceed = None
|
|
|
|
result = lib.AttachDebuggerTracing(
|
|
ctypes.c_int(show_debug_info),
|
|
ctypes.py_object(set_trace_func),
|
|
ctypes.py_object(tracing_func),
|
|
ctypes.c_uint(thread_ident),
|
|
ctypes.py_object(None),
|
|
)
|
|
if result != 0:
|
|
pydev_log.info('Unable to set tracing for existing threads. Result: %s' % result)
|
|
ret = result
|
|
finally:
|
|
if not IS_PY37_OR_GREATER:
|
|
set_interval(prev_value)
|
|
|
|
return ret
|