mirror of
https://github.com/microsoft/debugpy.git
synced 2025-12-23 08:48:12 +00:00
Add support for PyPy. Fixes #835
This commit is contained in:
parent
90298c4e79
commit
522f60cc3b
30 changed files with 3182 additions and 2979 deletions
|
|
@ -20,79 +20,86 @@ matrix:
|
|||
- python: 2.7
|
||||
env:
|
||||
- PYDEVD_USE_CYTHON=NO
|
||||
- PYDEVD_TEST_JYTHON=YES
|
||||
- PYDEVD_TEST_VM=JYTHON
|
||||
- JYTHON_URL=http://search.maven.org/remotecontent?filepath=org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar
|
||||
|
||||
# PyPy
|
||||
- python: 2.7
|
||||
env:
|
||||
- PYDEVD_PYTHON_VERSION=3.6
|
||||
- PYDEVD_USE_CYTHON=NO
|
||||
- PYDEVD_TEST_VM=PYPY
|
||||
|
||||
# Python 2.6 (with and without cython)
|
||||
- python: 2.7
|
||||
env:
|
||||
- PYDEVD_PYTHON_VERSION=2.6
|
||||
- PYDEVD_USE_CYTHON=NO
|
||||
- PYDEVD_TEST_JYTHON=NO
|
||||
- PYDEVD_TEST_VM=CPYTHON
|
||||
# - python: 2.7
|
||||
# env:
|
||||
# - PYDEVD_PYTHON_VERSION=2.6
|
||||
# - PYDEVD_USE_CYTHON=YES
|
||||
# - PYDEVD_TEST_JYTHON=NO
|
||||
# - PYDEVD_TEST_VM=CPYTHON
|
||||
|
||||
# Python 2.7 (with and without cython)
|
||||
- python: 2.7
|
||||
env:
|
||||
- PYDEVD_PYTHON_VERSION=2.7
|
||||
- PYDEVD_USE_CYTHON=NO
|
||||
- PYDEVD_TEST_JYTHON=NO
|
||||
# env:
|
||||
# - PYDEVD_PYTHON_VERSION=2.7
|
||||
# - PYDEVD_USE_CYTHON=NO
|
||||
# - PYDEVD_TEST_VM=CPYTHON
|
||||
- python: 2.7
|
||||
env:
|
||||
- PYDEVD_PYTHON_VERSION=2.7
|
||||
- PYDEVD_USE_CYTHON=YES
|
||||
- PYDEVD_TEST_JYTHON=NO
|
||||
- PYDEVD_TEST_VM=CPYTHON
|
||||
|
||||
# Python 3.5 (with and without cython)
|
||||
# - python: 2.7
|
||||
# env:
|
||||
# - PYDEVD_PYTHON_VERSION=3.5
|
||||
# - PYDEVD_USE_CYTHON=NO
|
||||
# - PYDEVD_TEST_JYTHON=NO
|
||||
# - PYDEVD_TEST_VM=CPYTHON
|
||||
- python: 2.7
|
||||
env:
|
||||
- PYDEVD_PYTHON_VERSION=3.5
|
||||
- PYDEVD_USE_CYTHON=YES
|
||||
- PYDEVD_TEST_JYTHON=NO
|
||||
- PYDEVD_TEST_VM=CPYTHON
|
||||
|
||||
# Python 3.6 (with and without cython)
|
||||
# - python: 2.7
|
||||
# env:
|
||||
# - PYDEVD_PYTHON_VERSION=3.6
|
||||
# - PYDEVD_USE_CYTHON=NO
|
||||
# - PYDEVD_TEST_JYTHON=NO
|
||||
- python: 2.7
|
||||
env:
|
||||
- PYDEVD_PYTHON_VERSION=3.6
|
||||
- PYDEVD_USE_CYTHON=YES
|
||||
- PYDEVD_TEST_JYTHON=NO
|
||||
- PYDEVD_USE_CYTHON=NO
|
||||
- PYDEVD_TEST_VM=CPYTHON
|
||||
# - python: 2.7
|
||||
# env:
|
||||
# - PYDEVD_PYTHON_VERSION=3.6
|
||||
# - PYDEVD_USE_CYTHON=YES
|
||||
# - PYDEVD_TEST_VM=CPYTHON
|
||||
|
||||
# Python 3.7 (with and without cython)
|
||||
- python: 2.7
|
||||
env:
|
||||
- PYDEVD_PYTHON_VERSION=3.7
|
||||
- PYDEVD_USE_CYTHON=NO
|
||||
- PYDEVD_TEST_JYTHON=NO
|
||||
# - python: 2.7
|
||||
# env:
|
||||
# - PYDEVD_PYTHON_VERSION=3.7
|
||||
# - PYDEVD_USE_CYTHON=YES
|
||||
# - PYDEVD_TEST_JYTHON=NO
|
||||
# - PYDEVD_USE_CYTHON=NO
|
||||
# - PYDEVD_TEST_VM=CPYTHON
|
||||
- python: 2.7
|
||||
env:
|
||||
- PYDEVD_PYTHON_VERSION=3.7
|
||||
- PYDEVD_USE_CYTHON=YES
|
||||
- PYDEVD_TEST_VM=CPYTHON
|
||||
|
||||
before_install:
|
||||
# CPython setup
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then chmod +x miniconda.sh; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then ./miniconda.sh -b; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then export PATH=/home/travis/miniconda2/bin:$PATH; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then conda update --yes conda; fi
|
||||
# CPython / Pypy setup
|
||||
- if [[ "$PYDEVD_TEST_VM" == "CPYTHON" || "$PYDEVD_TEST_VM" == "PYPY" ]]; then wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; fi
|
||||
- if [[ "$PYDEVD_TEST_VM" == "CPYTHON" || "$PYDEVD_TEST_VM" == "PYPY" ]]; then chmod +x miniconda.sh; fi
|
||||
- if [[ "$PYDEVD_TEST_VM" == "CPYTHON" || "$PYDEVD_TEST_VM" == "PYPY" ]]; then ./miniconda.sh -b; fi
|
||||
- if [[ "$PYDEVD_TEST_VM" == "CPYTHON" || "$PYDEVD_TEST_VM" == "PYPY" ]]; then export PATH=/home/travis/miniconda2/bin:$PATH; fi
|
||||
- if [[ "$PYDEVD_TEST_VM" == "CPYTHON" || "$PYDEVD_TEST_VM" == "PYPY" ]]; then conda update --yes conda; fi
|
||||
# Jython setup
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "YES" ]; then wget $JYTHON_URL -O jython_installer.jar; java -jar jython_installer.jar -s -d $HOME/jython; export PATH=$HOME/jython:$HOME/jython/bin:$PATH; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "YES" ]; then jython -c "print('')"; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "JYTHON" ]; then wget $JYTHON_URL -O jython_installer.jar; java -jar jython_installer.jar -s -d $HOME/jython; export PATH=$HOME/jython:$HOME/jython/bin:$PATH; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "JYTHON" ]; then jython -c "print('')"; fi
|
||||
# Fix issue with testGui
|
||||
- "export DISPLAY=:99.0"
|
||||
# Install packages
|
||||
|
|
@ -100,22 +107,28 @@ install:
|
|||
- sudo sysctl kernel.yama.ptrace_scope=0
|
||||
# Both
|
||||
- export PYTHONPATH=.
|
||||
# Python setup
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then conda create --yes -n build_env python=$PYDEVD_PYTHON_VERSION; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then source activate build_env; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then chmod +x ./.travis_install_python_deps.sh; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then ./.travis_install_python_deps.sh; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "NO" ]; then source activate build_env; python build_tools/build.py; fi
|
||||
# CPython setup
|
||||
- if [ "$PYDEVD_TEST_VM" == "CPYTHON" ]; then conda create --yes -n build_env python=$PYDEVD_PYTHON_VERSION; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "CPYTHON" ]; then source activate build_env; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "CPYTHON" ]; then chmod +x ./.travis_install_python_deps.sh; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "CPYTHON" ]; then ./.travis_install_python_deps.sh; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "CPYTHON" ]; then source activate build_env; python build_tools/build.py; fi
|
||||
# Pypy setup
|
||||
- if [ "$PYDEVD_TEST_VM" == "PYPY" ]; then conda create --yes -n build_env -c conda-forge pypy3.6; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "PYPY" ]; then source activate build_env; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "PYPY" ]; then chmod +x ./.travis_install_pypy_deps.sh; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "PYPY" ]; then ./.travis_install_pypy_deps.sh; fi
|
||||
# Jython setup
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "YES" ]; then chmod +x ./.travis_install_jython_deps.sh; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "YES" ]; then ./.travis_install_jython_deps.sh; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "JYTHON" ]; then chmod +x ./.travis_install_jython_deps.sh; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "JYTHON" ]; then ./.travis_install_jython_deps.sh; fi
|
||||
|
||||
# Run test
|
||||
# On local machine with jython: c:\bin\jython2.7.0\bin\jython.exe -Dpython.path=.;jython_test_deps/ant.jar;jython_test_deps/junit.jar -m pytest
|
||||
# On remove machine with python: c:\bin\python27\python.exe -m pytest
|
||||
script:
|
||||
# pytest-xdist not available for python == 2.6 and timing out without output with 2.7
|
||||
- if [[ ("$PYDEVD_TEST_JYTHON" == "NO") && ("$PYDEVD_PYTHON_VERSION" == "2.6" || "$PYDEVD_PYTHON_VERSION" == "2.7") ]]; then source activate build_env; python -m pytest; fi
|
||||
- if [[ ("$PYDEVD_TEST_JYTHON" == "NO") && ("$PYDEVD_PYTHON_VERSION" != "2.6" && "$PYDEVD_PYTHON_VERSION" != "2.7") ]]; then source activate build_env; python -m pytest -n auto; fi
|
||||
- if [ "$PYDEVD_TEST_JYTHON" == "YES" ]; then jython -Dpython.path=.:jython_test_deps/ant.jar:jython_test_deps/junit.jar -m pytest; fi
|
||||
- if [[ ("$PYDEVD_TEST_VM" == "CPYTHON") && ("$PYDEVD_PYTHON_VERSION" == "2.6" || "$PYDEVD_PYTHON_VERSION" == "2.7") ]]; then source activate build_env; python -m pytest; fi
|
||||
- if [[ ("$PYDEVD_TEST_VM" == "CPYTHON") && ("$PYDEVD_PYTHON_VERSION" != "2.6" && "$PYDEVD_PYTHON_VERSION" != "2.7") ]]; then source activate build_env; python -m pytest -n auto; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "PYPY" ]; then source activate build_env; pypy3 -m pytest -n auto; fi
|
||||
- if [ "$PYDEVD_TEST_VM" == "JYTHON" ]; then jython -Dpython.path=.:jython_test_deps/ant.jar:jython_test_deps/junit.jar -m pytest; fi
|
||||
|
||||
|
|
|
|||
15
src/ptvsd/_vendored/pydevd/.travis_install_pypy_deps.sh
Normal file
15
src/ptvsd/_vendored/pydevd/.travis_install_pypy_deps.sh
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
set -ev
|
||||
|
||||
source activate build_env
|
||||
|
||||
pypy3 -m ensurepip
|
||||
|
||||
pypy3 -m pip install pytest
|
||||
pypy3 -m pip install pytest-xdist
|
||||
pypy3 -m pip install pytest-timeout
|
||||
pypy3 -m pip install colorama
|
||||
pypy3 -m pip install psutil
|
||||
pypy3 -m pip install numpy
|
||||
pypy3 -m pip install ipython
|
||||
pypy3 -m pip install untangle
|
||||
|
|
@ -1,4 +1,8 @@
|
|||
include *.rst *.txt *.md LICENSE .travis.yml appveyor.yml *.pyx
|
||||
recursive-include pydevd_attach_to_process *.py *.dll *.so *.dylib *.txt *.c *.h *.bat Makefile *.sh *.pyx
|
||||
recursive-include _pydevd_bundle *.pyx
|
||||
include *.rst *.txt *.md LICENSE .travis.yml appveyor.yml *.pyx *.cpp *.hpp
|
||||
recursive-include pydevd_attach_to_process *.py *.dll *.so *.dylib *.txt *.c *.h *.bat Makefile *.sh *.pyx *.cpp *.hpp
|
||||
recursive-include pydevd_attach_to_process/common *.py *.dll *.so *.dylib *.txt *.c *.h *.bat Makefile *.sh *.pyx *.cpp *.hpp
|
||||
recursive-include pydevd_attach_to_process/linux_and_mac *.py *.dll *.so *.dylib *.txt *.c *.h *.bat Makefile *.sh *.pyx *.cpp *.hpp
|
||||
recursive-include pydevd_attach_to_process/winappdbg *.py *.dll *.so *.dylib *.txt *.c *.h *.bat Makefile *.sh *.pyx *.cpp *.hpp
|
||||
recursive-include pydevd_attach_to_process/windows *.py *.dll *.so *.dylib *.txt *.c *.h *.bat Makefile *.sh *.pyx *.cpp *.hpp
|
||||
recursive-include _pydevd_bundle *.pyx *.cpp *.hpp
|
||||
recursive-include build_tools *.py
|
||||
|
|
@ -192,6 +192,8 @@ def completions_to_xml(completions):
|
|||
msg = ["<xml>"]
|
||||
|
||||
for comp in completions:
|
||||
if IS_PY2:
|
||||
comp = [(x.encode('utf-8') if x.__class__ == unicode else x) for x in comp]
|
||||
msg.append('<comp p0="')
|
||||
msg.append(valid_xml(quote(comp[0], '/>_= \t')))
|
||||
msg.append('" p1="')
|
||||
|
|
|
|||
|
|
@ -43,7 +43,14 @@ def _pydevd_log(level, msg, *args):
|
|||
msg = msg % args
|
||||
except:
|
||||
msg = '%s - %s' % (msg, args)
|
||||
DebugInfoHolder.DEBUG_STREAM.write('%s\n' % (msg,))
|
||||
msg = '%s\n' % (msg,)
|
||||
try:
|
||||
DebugInfoHolder.DEBUG_STREAM.write(msg)
|
||||
except TypeError:
|
||||
if isinstance(msg, bytes):
|
||||
# Depending on the StringIO flavor, it may only accept unicode.
|
||||
msg = msg.decode('utf-8', 'replace')
|
||||
DebugInfoHolder.DEBUG_STREAM.write(msg)
|
||||
DebugInfoHolder.DEBUG_STREAM.flush()
|
||||
except:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import sys
|
||||
from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON, IS_IRONPYTHON
|
||||
from _pydevd_bundle.pydevd_constants import (STATE_RUN, PYTHON_SUSPEND, IS_JYTHON,
|
||||
USE_CUSTOM_SYS_CURRENT_FRAMES, USE_CUSTOM_SYS_CURRENT_FRAMES_MAP)
|
||||
from _pydev_bundle import pydev_log
|
||||
# IFDEF CYTHON
|
||||
# pydev_log.debug("Using Cython speedups")
|
||||
|
|
@ -9,7 +10,7 @@ from _pydevd_bundle.pydevd_frame import PyDBFrame
|
|||
|
||||
version = 11
|
||||
|
||||
if not hasattr(sys, '_current_frames'):
|
||||
if USE_CUSTOM_SYS_CURRENT_FRAMES:
|
||||
|
||||
# Some versions of Jython don't have it (but we can provide a replacement)
|
||||
if IS_JYTHON:
|
||||
|
|
@ -40,7 +41,7 @@ if not hasattr(sys, '_current_frames'):
|
|||
ret[thread.getId()] = frame
|
||||
return ret
|
||||
|
||||
elif IS_IRONPYTHON:
|
||||
elif USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
|
||||
_tid_to_last_frame = {}
|
||||
|
||||
# IronPython doesn't have it. Let's use our workaround...
|
||||
|
|
|
|||
|
|
@ -1130,8 +1130,11 @@ def build_exception_info_response(dbg, thread_id, request_seq, set_additional_th
|
|||
break
|
||||
frame = frame.f_back
|
||||
|
||||
while trace_obj.tb_next is not None:
|
||||
trace_obj = trace_obj.tb_next
|
||||
while True:
|
||||
tb_next = getattr(trace_obj, 'tb_next', None)
|
||||
if tb_next is None:
|
||||
break
|
||||
trace_obj = tb_next
|
||||
|
||||
info = dbg.suspended_frames_manager.get_topmost_frame_and_frame_id_to_line(thread_id)
|
||||
if info is not None:
|
||||
|
|
|
|||
|
|
@ -72,12 +72,17 @@ IS_64BIT_PROCESS = sys.maxsize > (2 ** 32)
|
|||
IS_JYTHON = pydevd_vm_type.get_vm_type() == pydevd_vm_type.PydevdVmType.JYTHON
|
||||
IS_JYTH_LESS25 = False
|
||||
|
||||
IS_PYPY = platform.python_implementation() == 'PyPy'
|
||||
|
||||
if IS_JYTHON:
|
||||
import java.lang.System # @UnresolvedImport
|
||||
IS_WINDOWS = java.lang.System.getProperty("os.name").lower().startswith("windows")
|
||||
if sys.version_info[0] == 2 and sys.version_info[1] < 5:
|
||||
IS_JYTH_LESS25 = True
|
||||
|
||||
USE_CUSTOM_SYS_CURRENT_FRAMES = not hasattr(sys, '_current_frames') or IS_PYPY
|
||||
USE_CUSTOM_SYS_CURRENT_FRAMES_MAP = USE_CUSTOM_SYS_CURRENT_FRAMES and (IS_PYPY or IS_IRONPYTHON)
|
||||
|
||||
IS_PYTHON_STACKLESS = "stackless" in sys.version.lower()
|
||||
CYTHON_SUPPORTED = False
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,7 +5,8 @@ from __future__ import print_function
|
|||
# DO NOT edit manually!
|
||||
# DO NOT edit manually!
|
||||
import sys
|
||||
from _pydevd_bundle.pydevd_constants import STATE_RUN, PYTHON_SUSPEND, IS_JYTHON, IS_IRONPYTHON
|
||||
from _pydevd_bundle.pydevd_constants import (STATE_RUN, PYTHON_SUSPEND, IS_JYTHON,
|
||||
USE_CUSTOM_SYS_CURRENT_FRAMES, USE_CUSTOM_SYS_CURRENT_FRAMES_MAP)
|
||||
from _pydev_bundle import pydev_log
|
||||
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
||||
pydev_log.debug("Using Cython speedups")
|
||||
|
|
@ -15,7 +16,7 @@ pydev_log.debug("Using Cython speedups")
|
|||
|
||||
version = 11
|
||||
|
||||
if not hasattr(sys, '_current_frames'):
|
||||
if USE_CUSTOM_SYS_CURRENT_FRAMES:
|
||||
|
||||
# Some versions of Jython don't have it (but we can provide a replacement)
|
||||
if IS_JYTHON:
|
||||
|
|
@ -46,7 +47,7 @@ if not hasattr(sys, '_current_frames'):
|
|||
ret[thread.getId()] = frame
|
||||
return ret
|
||||
|
||||
elif IS_IRONPYTHON:
|
||||
elif USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
|
||||
_tid_to_last_frame = {}
|
||||
|
||||
# IronPython doesn't have it. Let's use our workaround...
|
||||
|
|
@ -913,11 +914,13 @@ cdef class PyDBFrame:
|
|||
|
||||
# end trace_dispatch
|
||||
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
|
||||
from _pydev_bundle.pydev_log import exception as pydev_log_exception
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
from _pydevd_bundle.pydevd_constants import get_current_thread_id, IS_IRONPYTHON, NO_FTRACE
|
||||
from _pydevd_bundle.pydevd_constants import (get_current_thread_id, NO_FTRACE,
|
||||
USE_CUSTOM_SYS_CURRENT_FRAMES_MAP)
|
||||
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
|
||||
from _pydev_bundle.pydev_log import exception as pydev_log_exception
|
||||
|
||||
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
|
||||
from cpython.object cimport PyObject
|
||||
from cpython.ref cimport Py_INCREF, Py_XDECREF
|
||||
|
|
@ -1421,7 +1424,7 @@ cdef class ThreadTracer:
|
|||
return None if event == 'call' else NO_FTRACE
|
||||
|
||||
|
||||
if IS_IRONPYTHON:
|
||||
if USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from _pydevd_bundle.pydevd_net_command_factory_xml import NetCommandFactory
|
|||
from _pydevd_bundle.pydevd_utils import get_non_pydevd_threads
|
||||
import pydevd_file_utils
|
||||
from _pydevd_bundle.pydevd_comm import build_exception_info_response, pydevd_find_thread_by_id
|
||||
from _pydevd_bundle.pydevd_additional_thread_info_regular import set_additional_thread_info
|
||||
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
|
||||
|
||||
|
||||
class ModulesManager(object):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
from _pydev_bundle.pydev_is_thread_alive import is_thread_alive
|
||||
from _pydev_bundle.pydev_log import exception as pydev_log_exception
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
from _pydevd_bundle.pydevd_constants import get_current_thread_id, IS_IRONPYTHON, NO_FTRACE
|
||||
from _pydevd_bundle.pydevd_constants import (get_current_thread_id, NO_FTRACE,
|
||||
USE_CUSTOM_SYS_CURRENT_FRAMES_MAP)
|
||||
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
|
||||
from _pydev_bundle.pydev_log import exception as pydev_log_exception
|
||||
|
||||
# IFDEF CYTHON
|
||||
# from cpython.object cimport PyObject
|
||||
# from cpython.ref cimport Py_INCREF, Py_XDECREF
|
||||
|
|
@ -513,7 +515,7 @@ class ThreadTracer(object):
|
|||
return None if event == 'call' else NO_FTRACE
|
||||
|
||||
|
||||
if IS_IRONPYTHON:
|
||||
if USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ except:
|
|||
|
||||
import inspect
|
||||
import sys
|
||||
from _pydevd_bundle.pydevd_constants import IS_PY3K
|
||||
from _pydevd_bundle.pydevd_constants import IS_PY3K, USE_CUSTOM_SYS_CURRENT_FRAMES, IS_PYPY
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
|
||||
|
||||
|
|
@ -166,7 +166,12 @@ def dump_threads(stream=None):
|
|||
except:
|
||||
pass
|
||||
|
||||
from _pydevd_bundle.pydevd_additional_thread_info_regular import _current_frames
|
||||
if USE_CUSTOM_SYS_CURRENT_FRAMES and IS_PYPY:
|
||||
# On PyPy we can use its fake_frames to get the traceback
|
||||
# (instead of the actual real frames that need the tracing to be correct).
|
||||
_current_frames = sys._current_frames
|
||||
else:
|
||||
from _pydevd_bundle.pydevd_additional_thread_info_regular import _current_frames
|
||||
|
||||
stream.write('===============================================================================\n')
|
||||
stream.write('Threads running\n')
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ stop_frame_eval = None
|
|||
dummy_trace_dispatch = None
|
||||
clear_thread_local_info = None
|
||||
|
||||
use_cython = os.getenv('PYDEVD_USE_CYTHON', None)
|
||||
|
||||
# "NO" means we should not use frame evaluation, 'YES' we should use it (and fail if not there) and unspecified uses if possible.
|
||||
use_frame_eval = os.environ.get('PYDEVD_USE_FRAME_EVAL', None)
|
||||
|
||||
if use_frame_eval == 'NO':
|
||||
if use_frame_eval == 'NO' or use_cython == 'NO':
|
||||
pass
|
||||
|
||||
elif use_frame_eval == 'YES':
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import pytest
|
|||
import sys
|
||||
from _pydevd_bundle.pydevd_constants import IS_JYTHON, IS_IRONPYTHON
|
||||
from tests_python.debug_constants import TEST_CYTHON
|
||||
from tests_python.debug_constants import TEST_JYTHON
|
||||
from tests_python.debug_constants import PYDEVD_TEST_VM
|
||||
import site
|
||||
import os
|
||||
from _pydev_bundle import pydev_log
|
||||
|
|
@ -10,7 +10,7 @@ from _pydev_bundle import pydev_log
|
|||
|
||||
def pytest_report_header(config):
|
||||
print('PYDEVD_USE_CYTHON: %s' % (TEST_CYTHON,))
|
||||
print('PYDEVD_TEST_JYTHON: %s' % (TEST_JYTHON,))
|
||||
print('PYDEVD_TEST_VM: %s' % (PYDEVD_TEST_VM,))
|
||||
try:
|
||||
import multiprocessing
|
||||
except ImportError:
|
||||
|
|
@ -19,6 +19,7 @@ def pytest_report_header(config):
|
|||
print('Number of processors: %s' % (multiprocessing.cpu_count(),))
|
||||
|
||||
print('Relevant system paths:')
|
||||
print('sys.executable: %s' % (sys.executable,))
|
||||
print('sys.prefix: %s' % (sys.prefix,))
|
||||
|
||||
if hasattr(sys, 'base_prefix'):
|
||||
|
|
@ -193,7 +194,13 @@ _global_collect_info = False
|
|||
@pytest.yield_fixture(autouse=True)
|
||||
def before_after_each_function(request):
|
||||
global _global_collect_info
|
||||
import psutil
|
||||
|
||||
try:
|
||||
import psutil # Don't fail if not there
|
||||
except ImportError:
|
||||
yield
|
||||
return
|
||||
|
||||
current_pids = set(proc.pid for proc in psutil.process_iter())
|
||||
before_curr_proc_memory_info = psutil.Process().memory_info()
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ from _pydevd_bundle.pydevd_comm_constants import (CMD_THREAD_SUSPEND, CMD_STEP_I
|
|||
from _pydevd_bundle.pydevd_constants import (IS_JYTH_LESS25, get_thread_id, get_current_thread_id,
|
||||
dict_keys, dict_iter_items, DebugInfoHolder, PYTHON_SUSPEND, STATE_SUSPEND, STATE_RUN, get_frame,
|
||||
clear_cached_thread_id, INTERACTIVE_MODE_AVAILABLE, SHOW_DEBUG_INFO_ENV, IS_PY34_OR_GREATER, IS_PY2, NULL,
|
||||
NO_FTRACE, IS_IRONPYTHON, JSON_PROTOCOL, IS_CPYTHON, call_only_once)
|
||||
NO_FTRACE, IS_IRONPYTHON, JSON_PROTOCOL, IS_CPYTHON, USE_CUSTOM_SYS_CURRENT_FRAMES_MAP, call_only_once)
|
||||
from _pydevd_bundle.pydevd_defaults import PydevdCustomization
|
||||
from _pydevd_bundle.pydevd_custom_frames import CustomFramesContainer, custom_frames_container_init
|
||||
from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE, LIB_FILE
|
||||
|
|
@ -73,6 +73,9 @@ from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager
|
|||
from socket import SHUT_RDWR
|
||||
from _pydevd_bundle.pydevd_api import PyDevdAPI
|
||||
|
||||
if USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
|
||||
from _pydevd_bundle.pydevd_additional_thread_info_regular import _tid_to_last_frame
|
||||
|
||||
__version_info__ = (1, 3, 3)
|
||||
__version_info_str__ = []
|
||||
for v in __version_info__:
|
||||
|
|
@ -623,9 +626,12 @@ class PyDB(object):
|
|||
def _internal_get_file_type(self, abs_real_path_and_basename):
|
||||
basename = abs_real_path_and_basename[-1]
|
||||
if basename.startswith('<frozen '):
|
||||
# In Python 3.7 "<frozen ..." appear multiple times during import and should be
|
||||
# In Python 3.7 "<frozen ..." appears multiple times during import and should be
|
||||
# ignored for the user.
|
||||
return self.PYDEV_FILE
|
||||
if abs_real_path_and_basename[0].startswith('<builtin'):
|
||||
# In PyPy "<builtin> ..." can appear and should be ignored for the user.
|
||||
return self.PYDEV_FILE
|
||||
return self._dont_trace_get_file_type(basename)
|
||||
|
||||
def dont_trace_external_files(self, abs_path):
|
||||
|
|
@ -1583,6 +1589,8 @@ class PyDB(object):
|
|||
as the paused location on the top-level frame (exception info must be passed on 'arg').
|
||||
"""
|
||||
# print('do_wait_suspend %s %s %s %s' % (frame.f_lineno, frame.f_code.co_name, frame.f_code.co_filename, event))
|
||||
if USE_CUSTOM_SYS_CURRENT_FRAMES_MAP:
|
||||
_tid_to_last_frame[thread.ident] = sys._getframe()
|
||||
self.process_internal_commands()
|
||||
|
||||
thread_id = get_current_thread_id(thread)
|
||||
|
|
|
|||
|
|
@ -21,18 +21,21 @@ build_tools\pydevd_release_process.txt
|
|||
for release process.
|
||||
'''
|
||||
|
||||
|
||||
from setuptools import setup
|
||||
from setuptools.dist import Distribution
|
||||
from distutils.extension import Extension
|
||||
import os
|
||||
|
||||
|
||||
class BinaryDistribution(Distribution):
|
||||
|
||||
def is_pure(self):
|
||||
return False
|
||||
|
||||
|
||||
data_files = []
|
||||
|
||||
|
||||
def accept_file(f):
|
||||
f = f.lower()
|
||||
for ext in '.py .dll .so .dylib .txt .cpp .h .bat .c .sh .md .txt'.split():
|
||||
|
|
@ -41,6 +44,7 @@ def accept_file(f):
|
|||
|
||||
return f in ['readme', 'makefile']
|
||||
|
||||
|
||||
data_files.append(('pydevd_attach_to_process', [os.path.join('pydevd_attach_to_process', f) for f in os.listdir('pydevd_attach_to_process') if accept_file(f)]))
|
||||
for root, dirs, files in os.walk("pydevd_attach_to_process"):
|
||||
for d in dirs:
|
||||
|
|
@ -52,7 +56,7 @@ version = pydevd.__version__
|
|||
args = dict(
|
||||
name='pydevd',
|
||||
version=version,
|
||||
description = 'PyDev.Debugger (used in PyDev, PyCharm and VSCode Python)',
|
||||
description='PyDev.Debugger (used in PyDev, PyCharm and VSCode Python)',
|
||||
author='Fabio Zadrozny and others',
|
||||
url='https://github.com/fabioz/PyDev.Debugger/',
|
||||
license='EPL (Eclipse Public License)',
|
||||
|
|
@ -67,7 +71,7 @@ args = dict(
|
|||
|
||||
# 'pydev_sitecustomize', -- Not actually a package (not added)
|
||||
|
||||
# 'pydevd_attach_to_process', -- Not actually a package (included in MANIFEST.in)
|
||||
'pydevd_attach_to_process',
|
||||
|
||||
'pydevd_concurrency_analyser',
|
||||
'pydevd_plugins',
|
||||
|
|
@ -119,8 +123,6 @@ args = dict(
|
|||
zip_safe=False,
|
||||
)
|
||||
|
||||
|
||||
|
||||
import sys
|
||||
try:
|
||||
args_with_binaries = args.copy()
|
||||
|
|
@ -129,7 +131,7 @@ try:
|
|||
ext_modules=[
|
||||
# In this setup, don't even try to compile with cython, just go with the .c file which should've
|
||||
# been properly generated from a tested version.
|
||||
Extension('_pydevd_bundle.pydevd_cython', ["_pydevd_bundle/pydevd_cython.c",])
|
||||
Extension('_pydevd_bundle.pydevd_cython', ["_pydevd_bundle/pydevd_cython.c", ])
|
||||
]
|
||||
))
|
||||
setup(**args_with_binaries)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import unittest
|
|||
import pytest
|
||||
from _pydevd_bundle import pydevd_referrers
|
||||
from _pydev_bundle.pydev_imports import StringIO
|
||||
from tests_python.debugger_unittest import IS_PYPY
|
||||
|
||||
try:
|
||||
import gc
|
||||
|
|
@ -13,10 +14,10 @@ try:
|
|||
except NotImplementedError:
|
||||
has_referrers = False
|
||||
|
||||
# Only do get referrers tests if it's actually available.
|
||||
@pytest.mark.skipif(not has_referrers, reason='gc.get_referrers not implemented')
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
# Only do get referrers tests if it's actually available.
|
||||
@pytest.mark.skipif(not has_referrers or IS_PYPY, reason='gc.get_referrers not implemented')
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def test_get_referrers1(self):
|
||||
|
||||
|
|
@ -34,6 +35,7 @@ class Test(unittest.TestCase):
|
|||
def test_get_referrers2(self):
|
||||
|
||||
class MyClass(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
|
@ -48,10 +50,10 @@ class Test(unittest.TestCase):
|
|||
assert 'found_as="contained"' in result
|
||||
assert 'MyClass' in result
|
||||
|
||||
|
||||
def test_get_referrers3(self):
|
||||
|
||||
class MyClass(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
|
@ -66,10 +68,10 @@ class Test(unittest.TestCase):
|
|||
assert 'found_as="contained"' in result
|
||||
assert 'MyClass' in result
|
||||
|
||||
|
||||
def test_get_referrers4(self):
|
||||
|
||||
class MyClass(object):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
|
@ -78,20 +80,18 @@ class Test(unittest.TestCase):
|
|||
|
||||
# Let's see if we detect the cycle...
|
||||
result = pydevd_referrers.get_referrer_info(obj)
|
||||
assert 'found_as="me"' in result #Cyclic ref
|
||||
|
||||
assert 'found_as="me"' in result # Cyclic ref
|
||||
|
||||
def test_get_referrers5(self):
|
||||
container = dict(a=[1])
|
||||
|
||||
# Let's see if we detect the cycle...
|
||||
result = pydevd_referrers.get_referrer_info(container['a'])
|
||||
assert 'test_get_referrers5' not in result #I.e.: NOT in the current method
|
||||
assert 'test_get_referrers5' not in result # I.e.: NOT in the current method
|
||||
assert 'found_as="a"' in result
|
||||
assert 'dict' in result
|
||||
assert str(id(container)) in result
|
||||
|
||||
|
||||
def test_get_referrers6(self):
|
||||
import sys
|
||||
container = dict(a=[1])
|
||||
|
|
@ -107,12 +107,12 @@ class Test(unittest.TestCase):
|
|||
else:
|
||||
assert 'should_appear' in result
|
||||
|
||||
|
||||
def test_get_referrers7(self):
|
||||
|
||||
class MyThread(threading.Thread):
|
||||
|
||||
def run(self):
|
||||
#Note: we do that because if we do
|
||||
# Note: we do that because if we do
|
||||
self.frame = sys._getframe()
|
||||
|
||||
t = MyThread()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ try:
|
|||
except NameError:
|
||||
raw_input_name = 'input'
|
||||
|
||||
|
||||
#=======================================================================================================================
|
||||
# Test
|
||||
#=======================================================================================================================
|
||||
|
|
@ -27,9 +28,9 @@ class Test(unittest.TestCase):
|
|||
|
||||
try:
|
||||
client_port, _server_port = self.get_free_addresses()
|
||||
client_thread = self.start_client_thread(client_port) #@UnusedVariable
|
||||
client_thread = self.start_client_thread(client_port) # @UnusedVariable
|
||||
import time
|
||||
time.sleep(.3) #let's give it some time to start the threads
|
||||
time.sleep(.3) # let's give it some time to start the threads
|
||||
|
||||
from _pydev_bundle import pydev_localhost
|
||||
interpreter = pydevconsole.InterpreterInterface(pydev_localhost.get_localhost(), client_port, threading.currentThread())
|
||||
|
|
@ -39,16 +40,15 @@ class Test(unittest.TestCase):
|
|||
finally:
|
||||
sys.stdout = self.original_stdout
|
||||
|
||||
|
||||
def test_console_requests(self):
|
||||
self.original_stdout = sys.stdout
|
||||
sys.stdout = pydevd_io.IOBuf()
|
||||
|
||||
try:
|
||||
client_port, _server_port = self.get_free_addresses()
|
||||
client_thread = self.start_client_thread(client_port) #@UnusedVariable
|
||||
client_thread = self.start_client_thread(client_port) # @UnusedVariable
|
||||
import time
|
||||
time.sleep(.3) #let's give it some time to start the threads
|
||||
time.sleep(.3) # let's give it some time to start the threads
|
||||
|
||||
from _pydev_bundle import pydev_localhost
|
||||
from _pydev_bundle.pydev_console_utils import CodeFragment
|
||||
|
|
@ -66,9 +66,9 @@ class Test(unittest.TestCase):
|
|||
self.assertEqual(['50', 'input_request'], found)
|
||||
except:
|
||||
try:
|
||||
self.assertEqual(['input_request'], found) #IPython
|
||||
self.assertEqual(['input_request'], found) # IPython
|
||||
except:
|
||||
self.assertEqual([u'50', u'input_request'], found[1:]) # IPython 5.1
|
||||
self.assertEqual([u'50', u'input_request'], found[1:]) # IPython 5.1
|
||||
self.assertTrue(found[0].startswith(u'Out'))
|
||||
|
||||
comps = interpreter.getCompletions('foo.', 'foo.')
|
||||
|
|
@ -86,7 +86,6 @@ class Test(unittest.TestCase):
|
|||
'Did not find __add__ in : %s' % (comps,)
|
||||
)
|
||||
|
||||
|
||||
completions = interpreter.getCompletions('', '')
|
||||
for c in completions:
|
||||
if c[0] == 'AssertionError':
|
||||
|
|
@ -114,7 +113,7 @@ class Test(unittest.TestCase):
|
|||
desc.find('str(object=\'\') -> string') >= 0 or
|
||||
desc.find('str(value: Char*)') >= 0 or
|
||||
desc.find('str(object=\'\') -> str') >= 0 or
|
||||
desc.find('The most base type') >= 0 # Jython 2.7 is providing this :P
|
||||
desc.find('The most base type') >= 0 # Jython 2.7 is providing this :P
|
||||
,
|
||||
'Could not find what was needed in %s' % desc)
|
||||
|
||||
|
|
@ -127,20 +126,24 @@ class Test(unittest.TestCase):
|
|||
desc.find('str join(str self, list sequence)') >= 0 or
|
||||
desc.find('S.join(iterable) -> str') >= 0 or
|
||||
desc.find('join(self: str, sequence: list) -> str') >= 0 or
|
||||
desc.find('Concatenate any number of strings.') >= 0,
|
||||
desc.find('Concatenate any number of strings.') >= 0 or
|
||||
desc.find('bound method str.join') >= 0, # PyPy
|
||||
"Could not recognize: %s" % (desc,))
|
||||
finally:
|
||||
sys.stdout = self.original_stdout
|
||||
|
||||
|
||||
def start_client_thread(self, client_port):
|
||||
|
||||
class ClientThread(threading.Thread):
|
||||
|
||||
def __init__(self, client_port):
|
||||
threading.Thread.__init__(self)
|
||||
self.client_port = client_port
|
||||
|
||||
def run(self):
|
||||
|
||||
class HandleRequestInput:
|
||||
|
||||
def RequestInput(self):
|
||||
client_thread.requested_input = True
|
||||
return 'input_request'
|
||||
|
|
@ -164,13 +167,15 @@ class Test(unittest.TestCase):
|
|||
client_thread.start()
|
||||
return client_thread
|
||||
|
||||
|
||||
def start_debugger_server_thread(self, debugger_port, socket_code):
|
||||
|
||||
class DebuggerServerThread(threading.Thread):
|
||||
|
||||
def __init__(self, debugger_port, socket_code):
|
||||
threading.Thread.__init__(self)
|
||||
self.debugger_port = debugger_port
|
||||
self.socket_code = socket_code
|
||||
|
||||
def run(self):
|
||||
import socket
|
||||
s = socket.socket()
|
||||
|
|
@ -184,26 +189,26 @@ class Test(unittest.TestCase):
|
|||
debugger_thread.start()
|
||||
return debugger_thread
|
||||
|
||||
|
||||
def get_free_addresses(self):
|
||||
from _pydev_bundle.pydev_localhost import get_socket_names
|
||||
socket_names = get_socket_names(2, True)
|
||||
port0 = socket_names[0][1]
|
||||
port1 = socket_names[1][1]
|
||||
|
||||
|
||||
assert port0 != port1
|
||||
assert port0 > 0
|
||||
assert port1 > 0
|
||||
|
||||
return port0, port1
|
||||
|
||||
|
||||
def test_server(self):
|
||||
self.original_stdout = sys.stdout
|
||||
sys.stdout = pydevd_io.IOBuf()
|
||||
try:
|
||||
client_port, server_port = self.get_free_addresses()
|
||||
|
||||
class ServerThread(threading.Thread):
|
||||
|
||||
def __init__(self, client_port, server_port):
|
||||
threading.Thread.__init__(self)
|
||||
self.client_port = client_port
|
||||
|
|
@ -212,14 +217,15 @@ class Test(unittest.TestCase):
|
|||
def run(self):
|
||||
from _pydev_bundle import pydev_localhost
|
||||
pydevconsole.start_server(pydev_localhost.get_localhost(), self.server_port, self.client_port)
|
||||
|
||||
server_thread = ServerThread(client_port, server_port)
|
||||
server_thread.setDaemon(True)
|
||||
server_thread.start()
|
||||
|
||||
client_thread = self.start_client_thread(client_port) #@UnusedVariable
|
||||
client_thread = self.start_client_thread(client_port) # @UnusedVariable
|
||||
|
||||
import time
|
||||
time.sleep(.3) #let's give it some time to start the threads
|
||||
time.sleep(.3) # let's give it some time to start the threads
|
||||
sys.stdout = pydevd_io.IOBuf()
|
||||
|
||||
from _pydev_bundle import pydev_localhost
|
||||
|
|
|
|||
|
|
@ -8,12 +8,11 @@ import sys
|
|||
import unittest
|
||||
|
||||
try:
|
||||
import __builtin__ #@UnusedImport
|
||||
import __builtin__ # @UnusedImport
|
||||
BUILTIN_MOD = '__builtin__'
|
||||
except ImportError:
|
||||
BUILTIN_MOD = 'builtins'
|
||||
|
||||
|
||||
IS_JYTHON = sys.platform.find('java') != -1
|
||||
|
||||
HAS_WX = False
|
||||
|
|
@ -56,6 +55,8 @@ class TestCPython(unittest.TestCase):
|
|||
'(self, cmp: object, key: object, reverse: bool)',
|
||||
'(key=None, reverse=False)',
|
||||
'(self, key=None, reverse=False)',
|
||||
'(self, cmp, key, reverse)',
|
||||
'(self, key, reverse)',
|
||||
)
|
||||
|
||||
def test_imports2a(self):
|
||||
|
|
@ -74,7 +75,7 @@ class TestCPython(unittest.TestCase):
|
|||
|
||||
def test_imports2c(self):
|
||||
try:
|
||||
file # file is not available on py 3
|
||||
file # file is not available on py 3
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
|
|
@ -120,17 +121,38 @@ class TestCPython(unittest.TestCase):
|
|||
self.assert_in('RuntimeWarning' , tip)
|
||||
|
||||
# Remove cmp as it's not available on py 3
|
||||
#t = self.assert_in('cmp' , tip)
|
||||
#self.check_args(t, '(x, y)', '(object x, object y)', '(x: object, y: object)') #args
|
||||
# t = self.assert_in('cmp' , tip)
|
||||
# self.check_args(t, '(x, y)', '(object x, object y)', '(x: object, y: object)') #args
|
||||
|
||||
t = self.assert_in('isinstance' , tip)
|
||||
self.check_args(t, '(object, class_or_type_or_tuple)', '(object o, type typeinfo)', '(o: object, typeinfo: type)', '(obj, class_or_tuple)') #args
|
||||
self.check_args(
|
||||
t,
|
||||
'(object, class_or_type_or_tuple)',
|
||||
'(object o, type typeinfo)',
|
||||
'(o: object, typeinfo: type)',
|
||||
'(obj, class_or_tuple)',
|
||||
'(obj, klass_or_tuple)',
|
||||
) # args
|
||||
|
||||
t = self.assert_in('compile' , tip)
|
||||
self.check_args(t, '(source, filename, mode)', '()', '(o: object, name: str, val: object)', '(source, filename, mode, flags, dont_inherit, optimize)') #args
|
||||
self.check_args(
|
||||
t,
|
||||
'(source, filename, mode)',
|
||||
'()',
|
||||
'(o: object, name: str, val: object)',
|
||||
'(source, filename, mode, flags, dont_inherit, optimize)',
|
||||
'(source, filename, mode, flags, dont_inherit)',
|
||||
) # args
|
||||
|
||||
t = self.assert_in('setattr' , tip)
|
||||
self.check_args(t, '(object, name, value)', '(object o, str name, object val)', '(o: object, name: str, val: object)', '(obj, name, value)') #args
|
||||
self.check_args(
|
||||
t,
|
||||
'(object, name, value)',
|
||||
'(object o, str name, object val)',
|
||||
'(o: object, name: str, val: object)',
|
||||
'(obj, name, value)',
|
||||
'(object, name, val)',
|
||||
) # args
|
||||
|
||||
try:
|
||||
import compiler
|
||||
|
|
@ -142,7 +164,7 @@ class TestCPython(unittest.TestCase):
|
|||
except ImportError:
|
||||
compiler_module = None
|
||||
|
||||
if compiler_module is not None: #Not available in iron python
|
||||
if compiler_module is not None: # Not available in iron python
|
||||
tip = _pydev_imports_tipper.generate_tip(compiler_module)
|
||||
if compiler_module == 'compiler':
|
||||
self.assert_args('parse', '(buf, mode)', tip)
|
||||
|
|
@ -153,14 +175,12 @@ class TestCPython(unittest.TestCase):
|
|||
self.assert_args('walk', '(node)', tip)
|
||||
self.assert_in('parse' , tip)
|
||||
|
||||
|
||||
def check_args(self, t, *expected):
|
||||
for x in expected:
|
||||
if x == t[2]:
|
||||
return
|
||||
self.fail('Found: %s. Expected: %s' % (t[2], expected))
|
||||
|
||||
|
||||
def assert_args(self, tok, args, tips):
|
||||
for a in tips[1]:
|
||||
if tok == a[0]:
|
||||
|
|
@ -174,13 +194,11 @@ class TestCPython(unittest.TestCase):
|
|||
return a
|
||||
raise AssertionError('%s not in %s' % (tok, tips))
|
||||
|
||||
|
||||
def test_search(self):
|
||||
s = _pydev_imports_tipper.search_definition('inspect.ismodule')
|
||||
(f, line, col), foundAs = s
|
||||
self.assertTrue(line > 0)
|
||||
|
||||
|
||||
def test_dot_net_libraries(self):
|
||||
if sys.platform == 'cli':
|
||||
tip = _pydev_imports_tipper.generate_tip('System.Drawing')
|
||||
|
|
@ -189,10 +207,10 @@ class TestCPython(unittest.TestCase):
|
|||
tip = _pydev_imports_tipper.generate_tip('System.Drawing.Brushes')
|
||||
self.assert_in('Aqua' , tip)
|
||||
|
||||
|
||||
def test_inspect(self):
|
||||
|
||||
class C(object):
|
||||
|
||||
def metA(self, a, b):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
import platform
|
||||
|
||||
TEST_CYTHON = os.getenv('PYDEVD_USE_CYTHON', None) == 'YES'
|
||||
TEST_JYTHON = os.getenv('PYDEVD_TEST_JYTHON', None) == 'YES'
|
||||
PYDEVD_TEST_VM = os.getenv('PYDEVD_TEST_VM', None)
|
||||
|
||||
IS_PY3K = sys.version_info[0] >= 3
|
||||
IS_PY36_OR_GREATER = sys.version_info[0:2] >= (3, 6)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import pytest
|
|||
from tests_python import debugger_unittest
|
||||
from tests_python.debugger_unittest import (get_free_port, overrides, IS_CPYTHON, IS_JYTHON, IS_IRONPYTHON,
|
||||
IS_PY3K, CMD_ADD_DJANGO_EXCEPTION_BREAK, CMD_REMOVE_DJANGO_EXCEPTION_BREAK,
|
||||
CMD_ADD_EXCEPTION_BREAK, wait_for_condition)
|
||||
CMD_ADD_EXCEPTION_BREAK, wait_for_condition, IS_PYPY)
|
||||
from tests_python.debug_constants import IS_PY2
|
||||
from _pydevd_bundle.pydevd_comm_constants import file_system_encoding
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ class DebuggerRunnerSimple(debugger_unittest.DebuggerRunner):
|
|||
'org.python.util.jython'
|
||||
]
|
||||
|
||||
if IS_CPYTHON:
|
||||
if IS_CPYTHON or IS_PYPY:
|
||||
return [sys.executable, '-u']
|
||||
|
||||
if IS_IRONPYTHON:
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ import platform
|
|||
IS_CPYTHON = platform.python_implementation() == 'CPython'
|
||||
IS_IRONPYTHON = platform.python_implementation() == 'IronPython'
|
||||
IS_JYTHON = platform.python_implementation() == 'Jython'
|
||||
IS_PYPY = platform.python_implementation() == 'PyPy'
|
||||
IS_APPVEYOR = os.environ.get('APPVEYOR', '') in ('True', 'true', '1')
|
||||
|
||||
try:
|
||||
|
|
@ -589,6 +590,7 @@ class AbstractWriterThread(threading.Thread):
|
|||
'java.lang.UnsupportedOperationException',
|
||||
"RuntimeWarning: Parent module '_pydevd_bundle' not found while handling absolute import",
|
||||
'from _pydevd_bundle.pydevd_additional_thread_info_regular import _current_frames',
|
||||
'from _pydevd_bundle.pydevd_additional_thread_info import _current_frames',
|
||||
'import org.python.core as PyCore #@UnresolvedImport',
|
||||
'from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info',
|
||||
"RuntimeWarning: Parent module '_pydevd_bundle._debug_adapter' not found while handling absolute import",
|
||||
|
|
@ -780,7 +782,12 @@ class AbstractWriterThread(threading.Thread):
|
|||
# 10 seconds default timeout
|
||||
timeout = int(os.environ.get('PYDEVD_CONNECT_TIMEOUT', 10))
|
||||
s.settimeout(timeout)
|
||||
s.connect((host, port))
|
||||
for _i in range(6):
|
||||
try:
|
||||
s.connect((host, port))
|
||||
break
|
||||
except:
|
||||
time.sleep(.5) # We may have to wait a bit more and retry (especially on PyPy).
|
||||
s.settimeout(None) # no timeout after connected
|
||||
if SHOW_WRITES_AND_READS:
|
||||
print("Connected.")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
import threading, atexit, sys
|
||||
import time
|
||||
|
||||
try:
|
||||
from thread import start_new_thread
|
||||
except:
|
||||
from _thread import start_new_thread
|
||||
|
||||
|
||||
class MyError(Exception):
|
||||
|
||||
def __init__(self, msg):
|
||||
return Exception.__init__(self)
|
||||
|
||||
|
||||
def _atexit():
|
||||
print('TEST SUCEEDED')
|
||||
sys.stderr.write('TEST SUCEEDED\n')
|
||||
sys.stderr.flush()
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
# Register the TEST SUCEEDED msg to the exit of the process.
|
||||
atexit.register(_atexit)
|
||||
|
||||
|
||||
def thread_func():
|
||||
raise MyError('in thread 1')
|
||||
|
||||
|
||||
start_new_thread(thread_func, ())
|
||||
|
||||
# Wait for the first to be handled... otherwise, tests can become flaky if
|
||||
# both stop at the same time only 1 notification may be given for both, whereas
|
||||
# the test expects 2 notifications.
|
||||
time.sleep(.5)
|
||||
|
||||
|
||||
def thread_func2(n):
|
||||
raise MyError('in thread 2')
|
||||
|
||||
|
||||
th = threading.Thread(target=lambda: thread_func2(1))
|
||||
th.setDaemon(True)
|
||||
th.start()
|
||||
|
||||
th.join()
|
||||
|
||||
# This is a bit tricky: although we waited on the event, there's a slight chance
|
||||
# that we didn't get the notification because the thread could've stopped executing,
|
||||
# so, sleep a bit so that the test does not become flaky.
|
||||
time.sleep(.5)
|
||||
raise MyError('in main')
|
||||
|
|
@ -6,9 +6,12 @@ import pytest
|
|||
|
||||
from _pydevd_frame_eval.pydevd_modify_bytecode import insert_code
|
||||
from opcode import EXTENDED_ARG
|
||||
from tests_python.debugger_unittest import IS_PYPY
|
||||
from _pydevd_bundle.pydevd_constants import IS_PY37_OR_GREATER
|
||||
|
||||
TRACE_MESSAGE = "Trace called"
|
||||
|
||||
|
||||
def tracing():
|
||||
print(TRACE_MESSAGE)
|
||||
|
||||
|
|
@ -20,10 +23,11 @@ def call_tracing():
|
|||
def bar(a, b):
|
||||
return a + b
|
||||
|
||||
IS_PY36 = sys.version_info[0] == 3 and sys.version_info[1] == 6
|
||||
|
||||
IS_PY36 = sys.version_info[0] == 3 and sys.version_info[1] >= 6
|
||||
|
||||
|
||||
@pytest.mark.skipif(not IS_PY36, reason='Test requires Python 3.6')
|
||||
@pytest.mark.skipif(not IS_PY36 or IS_PYPY, reason='Test requires Python 3.6 onwards')
|
||||
class TestInsertCode(unittest.TestCase):
|
||||
lines_separator = "---Line tested---"
|
||||
|
||||
|
|
@ -82,7 +86,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
# Sometimes indexes of variable names and consts may be different, when we insert them, it's ok
|
||||
continue
|
||||
else:
|
||||
self.assertEquals(arg1, arg2, "Different arguments at offset {}".format(of))
|
||||
self.assertEqual(arg1, arg2, "Different arguments at offset {}".format(of))
|
||||
|
||||
def test_line(self):
|
||||
|
||||
|
|
@ -112,6 +116,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def original():
|
||||
a = 1
|
||||
b = 2
|
||||
|
|
@ -127,6 +132,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def original():
|
||||
n = 3
|
||||
sum = 0
|
||||
|
|
@ -144,6 +150,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def original():
|
||||
if True:
|
||||
a = 1
|
||||
|
|
@ -162,6 +169,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def original():
|
||||
if False:
|
||||
a = 1
|
||||
|
|
@ -180,6 +188,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def original():
|
||||
sum = 0
|
||||
for i in range(3):
|
||||
|
|
@ -231,6 +240,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def original():
|
||||
a = 5
|
||||
b = 0
|
||||
|
|
@ -302,6 +312,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def original():
|
||||
a = 1
|
||||
b = 3
|
||||
|
|
@ -338,7 +349,9 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
class A(object):
|
||||
|
||||
@staticmethod
|
||||
def foo():
|
||||
print("i'm in foo")
|
||||
|
|
@ -362,6 +375,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def foo():
|
||||
a = 1
|
||||
b = 2 # breakpoint
|
||||
|
|
@ -428,6 +442,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def foo():
|
||||
a = 1
|
||||
b = 1 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23 if a > 1 else 2 if a > 0 else 3 if a > 4 else 23
|
||||
|
|
@ -465,6 +480,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
finally:
|
||||
sys.stdout = self.original_stdout
|
||||
|
||||
@pytest.mark.skipif(IS_PY37_OR_GREATER, reason='It seems a new minor release of CPython 3.7 broke this (needs to be investigated).')
|
||||
def test_extended_arg_overflow(self):
|
||||
|
||||
from tests_python.resources._bytecode_overflow_example import Dummy, DummyTracing
|
||||
|
|
@ -476,6 +492,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
sys.stdout = StringIO()
|
||||
|
||||
try:
|
||||
|
||||
def foo():
|
||||
a = 1
|
||||
b = 2
|
||||
|
|
@ -554,7 +571,7 @@ class TestInsertCode(unittest.TestCase):
|
|||
b = b - 1 if a > 0 else b + 1
|
||||
b = b - 1 if a > 0 else b + 1
|
||||
b = b - 1 if a > 0 else b + 1
|
||||
not tracing() # add 'not' to balance EXTENDED_ARG when jumping
|
||||
not tracing() # add 'not' to balance EXTENDED_ARG when jumping
|
||||
b = b - 1 if a > 0 else b + 1
|
||||
b = b - 1 if a > 0 else b + 1
|
||||
b = b - 1 if a > 0 else b + 1
|
||||
|
|
@ -571,6 +588,5 @@ class TestInsertCode(unittest.TestCase):
|
|||
self.check_insert_to_line_by_symbols(foo, call_tracing, foo.__code__.co_firstlineno + 21,
|
||||
foo_check_2.__code__)
|
||||
|
||||
|
||||
finally:
|
||||
sys.stdout = self.original_stdout
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
import traceback
|
||||
|
||||
from _pydevd_bundle.pydevd_collect_try_except_info import collect_try_except_info
|
||||
from tests_python.debugger_unittest import IS_CPYTHON
|
||||
from tests_python.debugger_unittest import IS_CPYTHON, IS_PYPY
|
||||
|
||||
|
||||
def _method_call_with_error():
|
||||
|
|
@ -189,7 +189,7 @@ def test_collect_try_except_info2():
|
|||
|
||||
code = method.__code__
|
||||
lst = collect_try_except_info(code, use_func_first_line=True)
|
||||
if IS_CPYTHON:
|
||||
if IS_CPYTHON or IS_PYPY:
|
||||
assert str(lst) == '[{try:1 except 3 end block 5 raises: 5}]'
|
||||
else:
|
||||
assert lst == []
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ from tests_python.debugger_unittest import (CMD_SET_PROPERTY_TRACE, REASON_CAUGH
|
|||
IS_APPVEYOR, wait_for_condition, CMD_GET_FRAME, CMD_GET_BREAKPOINT_EXCEPTION,
|
||||
CMD_THREAD_SUSPEND, CMD_STEP_OVER, REASON_STEP_OVER, CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION,
|
||||
CMD_THREAD_RESUME_SINGLE_NOTIFICATION, REASON_STEP_RETURN, REASON_STEP_RETURN_MY_CODE,
|
||||
REASON_STEP_OVER_MY_CODE, REASON_STEP_INTO, CMD_THREAD_KILL)
|
||||
REASON_STEP_OVER_MY_CODE, REASON_STEP_INTO, CMD_THREAD_KILL, IS_PYPY)
|
||||
from _pydevd_bundle.pydevd_constants import IS_WINDOWS
|
||||
from _pydevd_bundle.pydevd_comm_constants import CMD_RELOAD_CODE
|
||||
import json
|
||||
|
|
@ -46,7 +46,7 @@ else:
|
|||
builtin_qualifier = "builtins"
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_IRONPYTHON or IS_JYTHON, reason='Test needs gc.get_referrers to really check anything.')
|
||||
@pytest.mark.skipif(not IS_CPYTHON, reason='Test needs gc.get_referrers/reference counting to really check anything.')
|
||||
def test_case_referrers(case_setup):
|
||||
with case_setup.test_file('_debugger_case1.py') as writer:
|
||||
writer.log.append('writing add breakpoint')
|
||||
|
|
@ -123,21 +123,26 @@ def test_case_2(case_setup):
|
|||
[['ValueError'], ['Exception']],
|
||||
[['Exception'], ['ValueError']], # ValueError will also suspend/print since we're dealing with a NameError
|
||||
)
|
||||
)
|
||||
)
|
||||
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',
|
||||
"NameError: name 'i' is not defined",
|
||||
'Traceback (most recent call last):',
|
||||
'File "<string>", line 1, in <module>',
|
||||
)
|
||||
|
||||
# It could be one or the other in PyPy depending on the version.
|
||||
msgs_one_in_stderr = (
|
||||
"NameError: name 'i' is not defined",
|
||||
"global name 'i' is not defined",
|
||||
)
|
||||
|
||||
def _ignore_stderr_line(line):
|
||||
if original_ignore_stderr_line(line):
|
||||
return True
|
||||
|
||||
for msg in msgs_in_stderr:
|
||||
for msg in msgs_in_stderr + msgs_one_in_stderr:
|
||||
if msg in line:
|
||||
return True
|
||||
|
||||
|
|
@ -148,8 +153,15 @@ def test_case_breakpoint_condition_exc(case_setup, skip_suspend_on_breakpoint_ex
|
|||
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:
|
||||
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:
|
||||
|
|
@ -2862,7 +2874,7 @@ def test_custom_frames(case_setup):
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
@pytest.mark.skipif((not (IS_PY36 or IS_PY27)) or IS_JYTHON, reason='Gevent only installed on Py36/Py27 for tests.')
|
||||
@pytest.mark.skipif((not (IS_PY36 or IS_PY27)) or IS_JYTHON or IS_PYPY, reason='Gevent only installed on Py36/Py27 for tests.')
|
||||
def test_gevent(case_setup):
|
||||
|
||||
def get_environ(writer):
|
||||
|
|
|
|||
|
|
@ -478,19 +478,23 @@ def test_case_handled_exception_breaks(case_setup):
|
|||
writer.finished_ok = True
|
||||
|
||||
|
||||
def test_case_unhandled_exception(case_setup):
|
||||
@pytest.mark.parametrize('target_file', [
|
||||
'_debugger_case_unhandled_exceptions.py',
|
||||
'_debugger_case_unhandled_exceptions_custom.py'
|
||||
])
|
||||
def test_case_unhandled_exception(case_setup, target_file):
|
||||
|
||||
def check_test_suceeded_msg(writer, stdout, stderr):
|
||||
# Don't call super (we have an unhandled exception in the stack trace).
|
||||
return 'TEST SUCEEDED' in ''.join(stdout) and 'TEST SUCEEDED' in ''.join(stderr)
|
||||
|
||||
def additional_output_checks(writer, stdout, stderr):
|
||||
if 'raise Exception' not in stderr:
|
||||
if 'raise MyError' not in stderr and 'raise Exception' not in stderr:
|
||||
raise AssertionError('Expected test to have an unhandled exception.\nstdout:\n%s\n\nstderr:\n%s' % (
|
||||
stdout, stderr))
|
||||
|
||||
with case_setup.test_file(
|
||||
'_debugger_case_unhandled_exceptions.py',
|
||||
target_file,
|
||||
check_test_suceeded_msg=check_test_suceeded_msg,
|
||||
additional_output_checks=additional_output_checks,
|
||||
EXPECTED_RETURNCODE=1,
|
||||
|
|
@ -505,15 +509,15 @@ def test_case_unhandled_exception(case_setup):
|
|||
line_in_thread2 = writer.get_line_index_with_content('in thread 2')
|
||||
line_in_main = writer.get_line_index_with_content('in main')
|
||||
json_facade.wait_for_thread_stopped(
|
||||
reason='exception', line=(line_in_thread1, line_in_thread2), file='_debugger_case_unhandled_exceptions.py')
|
||||
reason='exception', line=(line_in_thread1, line_in_thread2), file=target_file)
|
||||
json_facade.write_continue()
|
||||
|
||||
json_facade.wait_for_thread_stopped(
|
||||
reason='exception', line=(line_in_thread1, line_in_thread2), file='_debugger_case_unhandled_exceptions.py')
|
||||
reason='exception', line=(line_in_thread1, line_in_thread2), file=target_file)
|
||||
json_facade.write_continue()
|
||||
|
||||
json_facade.wait_for_thread_stopped(
|
||||
reason='exception', line=line_in_main, file='_debugger_case_unhandled_exceptions.py')
|
||||
reason='exception', line=line_in_main, file=target_file)
|
||||
json_facade.write_continue(wait_for_response=False)
|
||||
|
||||
writer.finished_ok = True
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ def test_dump_threads():
|
|||
pydevd.dump_threads(stream=stream)
|
||||
contents = stream.getvalue()
|
||||
assert 'Thread MainThread (daemon: False, pydevd thread: False)' in contents
|
||||
assert 'test_dump_threads' in contents
|
||||
assert 'test_dump_threads' in contents
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class Test(unittest.TestCase):
|
|||
def _do_analyze(self, files):
|
||||
invalid_files = []
|
||||
|
||||
p = subprocess.Popen(["python", self._coverage_file, "--pydev-analyze"],
|
||||
p = subprocess.Popen([sys.executable, self._coverage_file, "--pydev-analyze"],
|
||||
stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
__, stderrdata = p.communicate("|".join(files).encode())
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue