Vendor debugpy for VS Code attach debugging

Bundles debugpy 1.7.0 directly in python/ so "Maya: Attach (debugpy)"
works with no per-machine pip install step. Installed via Maya 2022's
own pip so it resolved a version actually compatible with Python 3.7
(Maya 2022's interpreter), then verified import + listen() succeeds
under all three target Maya Python versions (3.7/3.9/3.10).

Also fixes a latent __file__-under-exec() bug in
start_debug_server.py (same pitfall as Maya's own plugin loader,
never hit until this exercised it) and corrects the README's Maya
Python version claim -- 2022 ships Python 3.7, not 3.9 as previously
stated, which was never independently verified until now.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 09:01:16 +08:00
parent e2f2e6668a
commit ec5f52c7c6
623 changed files with 236815 additions and 12 deletions
@@ -0,0 +1,38 @@
import sys
import threading
from _pydev_bundle import pydev_log
def check():
with pydev_log.log_context(3, sys.stderr):
assert hasattr(sys, 'gettotalrefcount')
import pydevd_tracing
proceed1 = threading.Event()
proceed2 = threading.Event()
class SomeThread(threading.Thread):
def run(self):
proceed1.set()
proceed2.wait()
t = SomeThread()
t.start()
proceed1.wait()
try:
def some_func(frame, event, arg):
return some_func
pydevd_tracing.set_trace_to_threads(some_func)
finally:
proceed2.set()
lib = pydevd_tracing._load_python_helper_lib()
assert lib is None
print('Finished OK')
if __name__ == '__main__':
check()
@@ -0,0 +1,45 @@
import os
import sys
import platform
TEST_CYTHON = os.getenv('PYDEVD_USE_CYTHON', None) == 'YES'
PYDEVD_TEST_VM = os.getenv('PYDEVD_TEST_VM', None)
IS_PY36_OR_GREATER = sys.version_info[0:2] >= (3, 6)
IS_PY311_OR_GREATER = sys.version_info[0:2] >= (3, 11)
IS_CPYTHON = platform.python_implementation() == 'CPython'
TODO_PY311 = IS_PY311_OR_GREATER # Code which needs to be fixed in 3.11 should use this constant.
IS_PY36 = False
if sys.version_info[0] == 3 and sys.version_info[1] == 6:
IS_PY36 = True
TEST_DJANGO = False
TEST_FLASK = False
TEST_CHERRYPY = False
TEST_GEVENT = False
try:
import django
TEST_DJANGO = True
except:
pass
try:
import flask
TEST_FLASK = True
except:
pass
try:
import cherrypy
TEST_CHERRYPY = True
except:
pass
try:
import gevent
TEST_GEVENT = True
except:
pass
@@ -0,0 +1,575 @@
# coding: utf-8
from contextlib import contextmanager
import os
import pytest
from tests_python import debugger_unittest
from tests_python.debugger_unittest import (get_free_port, overrides, IS_CPYTHON, IS_JYTHON, IS_IRONPYTHON,
CMD_ADD_DJANGO_EXCEPTION_BREAK, CMD_REMOVE_DJANGO_EXCEPTION_BREAK,
CMD_ADD_EXCEPTION_BREAK, wait_for_condition, IS_PYPY)
from _pydevd_bundle.pydevd_comm_constants import file_system_encoding
import sys
from _pydevd_bundle.pydevd_constants import IS_WINDOWS
def get_java_location():
from java.lang import System # @UnresolvedImport
jre_dir = System.getProperty("java.home")
for f in [os.path.join(jre_dir, 'bin', 'java.exe'), os.path.join(jre_dir, 'bin', 'java')]:
if os.path.exists(f):
return f
raise RuntimeError('Unable to find java executable')
def get_jython_jar():
from java.lang import ClassLoader # @UnresolvedImport
cl = ClassLoader.getSystemClassLoader()
paths = map(lambda url: url.getFile(), cl.getURLs())
for p in paths:
if 'jython.jar' in p:
return p
raise RuntimeError('Unable to find jython.jar')
class _WriterThreadCaseMSwitch(debugger_unittest.AbstractWriterThread):
TEST_FILE = 'tests_python.resources._debugger_case_m_switch'
IS_MODULE = True
@overrides(debugger_unittest.AbstractWriterThread.get_environ)
def get_environ(self):
env = os.environ.copy()
curr_pythonpath = env.get('PYTHONPATH', '')
root_dirname = os.path.dirname(os.path.dirname(__file__))
curr_pythonpath += root_dirname + os.pathsep
env['PYTHONPATH'] = curr_pythonpath
return env
@overrides(debugger_unittest.AbstractWriterThread.get_main_filename)
def get_main_filename(self):
return debugger_unittest._get_debugger_test_file('_debugger_case_m_switch.py')
class _WriterThreadCaseModuleWithEntryPoint(_WriterThreadCaseMSwitch):
TEST_FILE = 'tests_python.resources._debugger_case_module_entry_point:main'
IS_MODULE = True
@overrides(_WriterThreadCaseMSwitch.get_main_filename)
def get_main_filename(self):
return debugger_unittest._get_debugger_test_file('_debugger_case_module_entry_point.py')
class AbstractWriterThreadCaseFlask(debugger_unittest.AbstractWriterThread):
FORCE_KILL_PROCESS_WHEN_FINISHED_OK = True
FLASK_FOLDER = None
TEST_FILE = 'flask'
IS_MODULE = True
def write_add_breakpoint_jinja2(self, line, func, template):
'''
@param line: starts at 1
'''
assert self.FLASK_FOLDER is not None
breakpoint_id = self.next_breakpoint_id()
template_file = debugger_unittest._get_debugger_test_file(os.path.join(self.FLASK_FOLDER, 'templates', template))
self.write("111\t%s\t%s\t%s\t%s\t%s\t%s\tNone\tNone" % (self.next_seq(), breakpoint_id, 'jinja2-line', template_file, line, func))
self.log.append('write_add_breakpoint_jinja: %s line: %s func: %s' % (breakpoint_id, line, func))
return breakpoint_id
def write_add_exception_breakpoint_jinja2(self, exception='jinja2-Exception'):
self.write('%s\t%s\t%s\t%s\t%s\t%s' % (CMD_ADD_EXCEPTION_BREAK, self.next_seq(), exception, 2, 0, 0))
@overrides(debugger_unittest.AbstractWriterThread.get_environ)
def get_environ(self):
import platform
env = os.environ.copy()
env['FLASK_APP'] = 'app.py'
env['FLASK_ENV'] = 'development'
env['FLASK_DEBUG'] = '0'
if platform.system() != 'Windows':
locale = 'en_US.utf8' if platform.system() == 'Linux' else 'en_US.UTF-8'
env.update({
'LC_ALL': locale,
'LANG': locale,
})
return env
def get_cwd(self):
return debugger_unittest._get_debugger_test_file(self.FLASK_FOLDER)
def get_command_line_args(self):
assert self.FLASK_FOLDER is not None
free_port = get_free_port()
self.flask_port = free_port
return [
'flask',
'run',
'--no-debugger',
'--no-reload',
'--with-threads',
'--port',
str(free_port),
]
def _ignore_stderr_line(self, line):
if debugger_unittest.AbstractWriterThread._ignore_stderr_line(self, line):
return True
if 'Running on http:' in line:
return True
if 'GET / HTTP/' in line:
return True
return False
def create_request_thread(self, url=''):
return debugger_unittest.AbstractWriterThread.create_request_thread(
self, 'http://127.0.0.1:%s%s' % (self.flask_port, url))
class AbstractWriterThreadCaseDjango(debugger_unittest.AbstractWriterThread):
FORCE_KILL_PROCESS_WHEN_FINISHED_OK = True
DJANGO_FOLDER = None
def _ignore_stderr_line(self, line):
if debugger_unittest.AbstractWriterThread._ignore_stderr_line(self, line):
return True
if 'GET /my_app' in line:
return True
return False
def get_command_line_args(self):
assert self.DJANGO_FOLDER is not None
free_port = get_free_port()
self.django_port = free_port
return [
debugger_unittest._get_debugger_test_file(os.path.join(self.DJANGO_FOLDER, 'manage.py')),
'runserver',
'--noreload',
'--nothreading',
str(free_port),
]
def write_add_breakpoint_django(self, line, func, template):
'''
@param line: starts at 1
'''
assert self.DJANGO_FOLDER is not None
breakpoint_id = self.next_breakpoint_id()
template_file = debugger_unittest._get_debugger_test_file(os.path.join(self.DJANGO_FOLDER, 'my_app', 'templates', 'my_app', template))
self.write("111\t%s\t%s\t%s\t%s\t%s\t%s\tNone\tNone" % (self.next_seq(), breakpoint_id, 'django-line', template_file, line, func))
self.log.append('write_add_django_breakpoint: %s line: %s func: %s' % (breakpoint_id, line, func))
return breakpoint_id
def write_add_exception_breakpoint_django(self, exception='Exception'):
self.write('%s\t%s\t%s' % (CMD_ADD_DJANGO_EXCEPTION_BREAK, self.next_seq(), exception))
def write_remove_exception_breakpoint_django(self, exception='Exception'):
self.write('%s\t%s\t%s' % (CMD_REMOVE_DJANGO_EXCEPTION_BREAK, self.next_seq(), exception))
def create_request_thread(self, url=''):
return debugger_unittest.AbstractWriterThread.create_request_thread(
self, 'http://127.0.0.1:%s/%s' % (self.django_port, url))
class DebuggerRunnerSimple(debugger_unittest.DebuggerRunner):
def get_command_line(self):
if IS_JYTHON:
if sys.executable is not None:
# i.e.: we're running with the provided jython.exe
return [sys.executable]
else:
return [
get_java_location(),
'-classpath',
get_jython_jar(),
'org.python.util.jython'
]
if IS_CPYTHON or IS_PYPY:
return [sys.executable, '-u']
if IS_IRONPYTHON:
return [
sys.executable,
'-X:Frames'
]
raise RuntimeError('Unable to provide command line')
class DebuggerRunnerRemote(debugger_unittest.DebuggerRunner):
def get_command_line(self):
return [sys.executable, '-u']
def add_command_line_args(self, args, dap=False):
writer = self.writer
ret = args + [self.writer.TEST_FILE]
ret = writer.update_command_line_args(ret) # Provide a hook for the writer
return ret
@pytest.fixture
def debugger_runner_simple(tmpdir):
return DebuggerRunnerSimple(tmpdir)
@pytest.fixture
def debugger_runner_remote(tmpdir):
return DebuggerRunnerRemote(tmpdir)
@pytest.fixture
def case_setup(tmpdir, debugger_runner_simple):
runner = debugger_runner_simple
class WriterThread(debugger_unittest.AbstractWriterThread):
pass
class CaseSetup(object):
check_non_ascii = False
NON_ASCII_CHARS = u'áéíóú汉字'
dap = False
@contextmanager
def test_file(
self,
filename,
wait_for_port=True,
wait_for_initialization=True,
**kwargs
):
import shutil
filename = debugger_unittest._get_debugger_test_file(filename)
if self.check_non_ascii:
basedir = str(tmpdir)
if isinstance(basedir, bytes):
basedir = basedir.decode('utf-8')
if isinstance(filename, bytes):
filename = filename.decode('utf-8')
new_dir = os.path.join(basedir, self.NON_ASCII_CHARS)
os.makedirs(new_dir)
new_filename = os.path.join(new_dir, self.NON_ASCII_CHARS + os.path.basename(filename))
shutil.copyfile(filename, new_filename)
filename = new_filename
WriterThread.TEST_FILE = filename
for key, value in kwargs.items():
assert hasattr(WriterThread, key)
setattr(WriterThread, key, value)
with runner.check_case(
WriterThread,
wait_for_port=wait_for_port,
wait_for_initialization=wait_for_initialization,
dap=self.dap
) as writer:
yield writer
return CaseSetup()
@pytest.fixture
def case_setup_dap(case_setup):
case_setup.dap = True
return case_setup
@pytest.fixture
def case_setup_unhandled_exceptions(case_setup):
original = case_setup.test_file
def check_test_suceeded_msg(writer, stdout, stderr):
return 'TEST SUCEEDED' in ''.join(stderr)
def additional_output_checks(writer, stdout, stderr):
# Don't call super as we have an expected exception
if 'ValueError: TEST SUCEEDED' not in stderr:
raise AssertionError('"ValueError: TEST SUCEEDED" not in stderr.\nstdout:\n%s\n\nstderr:\n%s' % (
stdout, stderr))
def test_file(*args, **kwargs):
kwargs.setdefault('check_test_suceeded_msg', check_test_suceeded_msg)
kwargs.setdefault('additional_output_checks', additional_output_checks)
return original(*args, **kwargs)
case_setup.test_file = test_file
return case_setup
@pytest.fixture
def case_setup_remote(debugger_runner_remote):
class WriterThread(debugger_unittest.AbstractWriterThread):
pass
class CaseSetup(object):
dap = False
@contextmanager
def test_file(
self,
filename,
wait_for_port=True,
access_token=None,
client_access_token=None,
append_command_line_args=(),
**kwargs
):
def update_command_line_args(writer, args):
ret = debugger_unittest.AbstractWriterThread.update_command_line_args(writer, args)
wait_for_condition(lambda: hasattr(writer, 'port'))
ret.append(str(writer.port))
if access_token is not None:
ret.append('--access-token')
ret.append(access_token)
if client_access_token is not None:
ret.append('--client-access-token')
ret.append(client_access_token)
if self.dap:
ret.append('--use-dap-mode')
ret.extend(append_command_line_args)
return ret
WriterThread.TEST_FILE = debugger_unittest._get_debugger_test_file(filename)
WriterThread.update_command_line_args = update_command_line_args
for key, value in kwargs.items():
assert hasattr(WriterThread, key)
setattr(WriterThread, key, value)
with debugger_runner_remote.check_case(WriterThread, wait_for_port=wait_for_port) as writer:
yield writer
return CaseSetup()
@pytest.fixture
def case_setup_remote_dap(case_setup_remote):
case_setup_remote.dap = True
return case_setup_remote
@pytest.fixture
def case_setup_remote_attach_to_dap(debugger_runner_remote):
'''
The difference from this to case_setup_remote is that this one will connect to a server
socket started by the debugger and case_setup_remote will create the server socket and wait
for a connection from the debugger.
'''
class WriterThread(debugger_unittest.AbstractWriterThread):
@overrides(debugger_unittest.AbstractWriterThread.run)
def run(self):
# I.e.: don't start socket on start(), rather, the test should call
# start_socket_client() when needed.
pass
class CaseSetup(object):
dap = True
@contextmanager
def test_file(
self,
filename,
port,
**kwargs
):
additional_args = kwargs.pop('additional_args', [])
def update_command_line_args(writer, args):
ret = debugger_unittest.AbstractWriterThread.update_command_line_args(writer, args)
ret.append(str(port))
if self.dap:
ret.append('--use-dap-mode')
ret.extend(additional_args)
return ret
WriterThread.TEST_FILE = debugger_unittest._get_debugger_test_file(filename)
WriterThread.update_command_line_args = update_command_line_args
for key, value in kwargs.items():
assert hasattr(WriterThread, key)
setattr(WriterThread, key, value)
with debugger_runner_remote.check_case(WriterThread, wait_for_port=False) as writer:
yield writer
return CaseSetup()
@pytest.fixture
def case_setup_multiprocessing(debugger_runner_simple):
class WriterThread(debugger_unittest.AbstractWriterThread):
pass
class CaseSetup(object):
dap = False
@contextmanager
def test_file(
self,
filename,
**kwargs
):
def update_command_line_args(writer, args):
ret = debugger_unittest.AbstractWriterThread.update_command_line_args(writer, args)
ret.insert(ret.index('--client'), '--multiprocess')
if self.dap:
ret.insert(ret.index('--client'), '--debug-mode')
ret.insert(ret.index('--client'), 'debugpy-dap')
ret.insert(ret.index('--client'), '--json-dap-http')
return ret
WriterThread.update_command_line_args = update_command_line_args
WriterThread.TEST_FILE = debugger_unittest._get_debugger_test_file(filename)
for key, value in kwargs.items():
assert hasattr(WriterThread, key)
setattr(WriterThread, key, value)
with debugger_runner_simple.check_case(WriterThread) as writer:
yield writer
return CaseSetup()
@pytest.fixture
def case_setup_multiprocessing_dap(case_setup_multiprocessing):
case_setup_multiprocessing.dap = True
return case_setup_multiprocessing
@pytest.fixture
def case_setup_m_switch(debugger_runner_simple):
class WriterThread(_WriterThreadCaseMSwitch):
pass
class CaseSetup(object):
@contextmanager
def test_file(self, **kwargs):
for key, value in kwargs.items():
assert hasattr(WriterThread, key)
setattr(WriterThread, key, value)
with debugger_runner_simple.check_case(WriterThread) as writer:
yield writer
return CaseSetup()
@pytest.fixture
def case_setup_m_switch_entry_point(debugger_runner_simple):
runner = debugger_runner_simple
class WriterThread(_WriterThreadCaseModuleWithEntryPoint):
pass
class CaseSetup(object):
@contextmanager
def test_file(self, **kwargs):
for key, value in kwargs.items():
assert hasattr(WriterThread, key)
setattr(WriterThread, key, value)
with runner.check_case(WriterThread) as writer:
yield writer
return CaseSetup()
@pytest.fixture
def case_setup_django(debugger_runner_simple):
class WriterThread(AbstractWriterThreadCaseDjango):
pass
class CaseSetup(object):
dap = False
@contextmanager
def test_file(self, **kwargs):
import django
version = [int(x) for x in django.get_version().split('.')][:2]
if version == [1, 7]:
django_folder = 'my_django_proj_17'
elif version in ([2, 1], [2, 2], [3, 0], [3, 1], [3, 2], [4, 0], [4, 1]):
django_folder = 'my_django_proj_21'
else:
raise AssertionError('Can only check django 1.7 -> 4.1 right now. Found: %s' % (version,))
WriterThread.DJANGO_FOLDER = django_folder
for key, value in kwargs.items():
assert hasattr(WriterThread, key)
setattr(WriterThread, key, value)
with debugger_runner_simple.check_case(WriterThread, dap=self.dap) as writer:
yield writer
return CaseSetup()
@pytest.fixture
def case_setup_django_dap(case_setup_django):
case_setup_django.dap = True
return case_setup_django
@pytest.fixture
def case_setup_flask(debugger_runner_simple):
class WriterThread(AbstractWriterThreadCaseFlask):
pass
class CaseSetup(object):
dap = False
@contextmanager
def test_file(self, **kwargs):
WriterThread.FLASK_FOLDER = 'flask1'
for key, value in kwargs.items():
assert hasattr(WriterThread, key)
setattr(WriterThread, key, value)
with debugger_runner_simple.check_case(WriterThread, dap=self.dap) as writer:
yield writer
return CaseSetup()
@pytest.fixture
def case_setup_flask_dap(case_setup_flask):
case_setup_flask.dap = True
return case_setup_flask
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,60 @@
from flask import Flask
from flask import render_template
app = Flask(__name__)
@app.route("/")
def home():
content = 'Flask-Jinja-Test'
return render_template(
"hello.html",
title='Hello',
content=content
)
@app.route("/handled")
def bad_route_handled():
try:
raise ArithmeticError('Hello')
except Exception:
pass
return render_template(
"hello.html",
title='Hello',
content='Flask-Jinja-Test'
)
@app.route("/unhandled")
def bad_route_unhandled():
raise ArithmeticError('Hello')
return render_template(
"hello.html",
title='Hello',
content='Flask-Jinja-Test'
)
@app.route("/bad_template")
def bad_template():
return render_template(
"bad.html",
title='Bad',
content='Flask-Jinja-Test'
)
@app.route("/exit")
def exit_app():
from flask import request
func = request.environ.get('werkzeug.server.shutdown')
if func is None:
raise RuntimeError('No shutdown')
func()
return 'Done'
if __name__ == '__main__':
app.run()
@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_django_proj_17.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
@@ -0,0 +1,4 @@
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
@@ -0,0 +1,12 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^name$', views.get_name, name='name'),
url(r'^template_error$', views.template_error, name='template_error'),
url(r'^template_error2$', views.template_error2, name='template_error2'),
url(r'^inherits$', views.inherits, name='inherits'),
url(r'^no_var_error$', views.no_var_error, name='no_var_error'),
]
@@ -0,0 +1,73 @@
from django.shortcuts import render
# Create your views here.
from django.http import HttpResponse, HttpResponseRedirect
import sys
from .forms import NameForm
class Entry(object):
def __init__(self, key, val):
self.key = key
self.val = val
def __unicode__(self):
return u'%s:%s' % (self.key, self.val)
def __str__(self):
return u'%s:%s' % (self.key, self.val)
def index(request):
context = {
'entries': [Entry('v1', 'v1'), Entry('v2', 'v2')]
}
ret = render(request, 'my_app/index.html', context)
return ret
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm(data={'your_name': 'unknown name'})
return render(request, 'my_app/name.html', {'form': form})
def inherits(request):
context = {}
ret = render(request, 'my_app/inherits.html', context)
return ret
def template_error(request):
context = {
'entries': [Entry('v1', 'v1'), Entry('v2', 'v2')]
}
ret = render(request, 'my_app/template_error.html', context)
return ret
def template_error2(request):
context = {}
ret = render(request, 'my_app/template_error2.html', context)
return ret
def no_var_error(request):
context = {}
ret = render(request, 'my_app/no_var_error.html', context)
return ret
@@ -0,0 +1,86 @@
"""
Django settings for my_django_proj_17 project.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'Placeholder_5_sue9bp&j=45#%_hcx3f34k!qnt$mxfd&7zq@7c7t@sn4_l)b'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'my_app',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'my_django_proj_17.urls'
WSGI_APPLICATION = 'my_django_proj_17.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
# No database for our test.
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
STATIC_URL = '/static/'
@@ -0,0 +1,11 @@
from django.conf.urls import patterns, include, url
from django.contrib import admin
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'my_django_proj_17.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^my_app/', include('my_app.urls')),
)
@@ -0,0 +1,14 @@
"""
WSGI config for my_django_proj_17 project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_django_proj_17.settings")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
@@ -0,0 +1,15 @@
#!/usr/bin/env python
import os
import sys
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'my_django_proj_21.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'my_app'
@@ -0,0 +1,4 @@
from django import forms
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.
@@ -0,0 +1,72 @@
'''
Note: run test with manage.py test my_app
This is mostly for experimenting.
The actual code used is mostly a copy of this that lives in `django_debug.py`.
'''
from django.test import SimpleTestCase
def collect_lines_for_django_template(template_contents):
from django import template
t = template.Template(template_contents)
return _collect_valid_lines_in_django_template(t)
def _collect_valid_lines_in_django_template(template):
lines = set()
for node in _iternodes(template.nodelist):
lineno = _get_lineno(node)
if lineno is not None:
lines.add(lineno)
return lines
def _get_lineno(node):
if hasattr(node, 'token') and hasattr(node.token, 'lineno'):
return node.token.lineno
return None
def _iternodes(nodelist):
for node in nodelist:
yield node
try:
children = node.child_nodelists
except:
pass
else:
for attr in children:
nodelist = getattr(node, attr, None)
if nodelist:
# i.e.: yield from _iternodes(nodelist)
for node in _iternodes(nodelist):
yield node
class MyTest(SimpleTestCase):
def test_something(self):
template_contents = '''{% if entries %}
<ul>
{% for entry in entries %}
{% for entry in entries2 %}
<li>
{{ entry.key }}
:
{{ entry.val }}
</li>
{% endfor %}
{% endfor %}
</ul>
{% else %}
<p>No entries are available.</p>
{% endif %}'''
self.assertEqual(
collect_lines_for_django_template(template_contents),
{1, 3, 4, 6, 8, 10, 11, 13}
)
@@ -0,0 +1,15 @@
try:
from django.conf.urls import url
except ImportError:
from django.urls import re_path as url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^name$', views.get_name, name='name'),
url(r'^template_error2$', views.template_error2, name='template_error2'),
url(r'^template_error$', views.template_error, name='template_error'),
url(r'^inherits$', views.inherits, name='inherits'),
url(r'^no_var_error$', views.no_var_error, name='no_var_error'),
]
@@ -0,0 +1,80 @@
from django.shortcuts import render
# Create your views here.
from django.http import HttpResponse, HttpResponseRedirect
import sys
from .forms import NameForm
class Entry(object):
def __init__(self, key, val):
self.key = key
self.val = val
def __unicode__(self):
return u'%s:%s' % (self.key, self.val)
def __str__(self):
return u'%s:%s' % (self.key, self.val)
def index(request):
import faulthandler
faulthandler.enable()
context = {
'entries': [Entry('v1', 'v1'), Entry('v2', 'v2')]
}
ret = render(request, 'my_app/index.html', context)
return ret
def get_name(request):
import faulthandler
faulthandler.enable()
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
form = NameForm(data={'your_name': 'unknown name'})
return render(request, 'my_app/name.html', {'form': form})
def template_error(request):
import faulthandler
faulthandler.enable()
context = {
'entries': [Entry('v1', 'v1'), Entry('v2', 'v2')]
}
ret = render(request, 'my_app/template_error.html', context)
return ret
def template_error2(request):
import faulthandler
faulthandler.enable()
context = {}
ret = render(request, 'my_app/template_error2.html', context)
return ret
def inherits(request):
context = {}
ret = render(request, 'my_app/inherits.html', context)
return ret
def no_var_error(request):
context = {}
ret = render(request, 'my_app/no_var_error.html', context)
return ret
@@ -0,0 +1,120 @@
"""
Django settings for my_django_proj_21 project.
Generated by 'django-admin startproject' using Django 2.1.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.1/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'Placeholder_u1jqdxv=z@ue9)%onkenaqb*&4dzd2mmb98#j*8uq^fn#j67)p'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'my_app',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'my_django_proj_21.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'my_django_proj_21.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
# No database for our test.
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
@@ -0,0 +1,23 @@
"""my_django_proj_21 URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls.conf import include
urlpatterns = [
path('admin/', admin.site.urls),
path('my_app/', include('my_app.urls')),
]
@@ -0,0 +1,16 @@
"""
WSGI config for my_django_proj_21 project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'my_django_proj_21.settings')
application = get_wsgi_application()
@@ -0,0 +1,18 @@
from _pydevd_bundle.pydevd_extension_api import DebuggerEventHandler
import os
import sys
class VerifyEvent(object):
def on_debugger_modules_loaded(self, **kwargs):
print ("INITIALIZE EVENT RECEIVED")
# check that some core modules are loaded before this callback is invoked
modules_loaded = all(mod in sys.modules for mod in ('pydevd_file_utils', '_pydevd_bundle.pydevd_constants'))
if modules_loaded:
print ("TEST SUCEEDED") # incorrect spelling on purpose
else:
print ("TEST FAILED")
if os.environ.get("VERIFY_EVENT_TEST"):
DebuggerEventHandler.register(VerifyEvent)
@@ -0,0 +1,20 @@
from _pydevd_bundle.pydevd_extension_api import StrPresentationProvider, TypeResolveProvider
class RectResolver(TypeResolveProvider):
def get_dictionary(self, var):
return {'length': var.length, 'width': var.width, 'area': var.length * var.width}
def resolve(self, var, attribute):
return getattr(var, attribute, None) if attribute != 'area' else var.length * var.width
def can_provide(self, type_object, type_name):
return type_name.endswith('Rect')
class RectToString(StrPresentationProvider):
def get_str(self, val):
return "Rectangle[Length: %s, Width: %s , Area: %s]" % (val.length, val.width, val.length * val.width)
def can_provide(self, type_object, type_name):
return type_name.endswith('Rect')
@@ -0,0 +1,245 @@
from tests_python import debugger_unittest
import sys
import re
import os
CHECK_BASELINE, CHECK_REGULAR, CHECK_CYTHON, CHECK_FRAME_EVAL = 'baseline', 'regular', 'cython', 'frame_eval'
pytest_plugins = [
str('tests_python.debugger_fixtures'),
]
RUNS = 5
class PerformanceWriterThread(debugger_unittest.AbstractWriterThread):
CHECK = None
debugger_unittest.AbstractWriterThread.get_environ # overrides
def get_environ(self):
env = os.environ.copy()
if self.CHECK == CHECK_BASELINE:
env['PYTHONPATH'] = r'X:\PyDev.Debugger.baseline'
elif self.CHECK == CHECK_CYTHON:
env['PYDEVD_USE_CYTHON'] = 'YES'
env['PYDEVD_USE_FRAME_EVAL'] = 'NO'
elif self.CHECK == CHECK_FRAME_EVAL:
env['PYDEVD_USE_CYTHON'] = 'YES'
env['PYDEVD_USE_FRAME_EVAL'] = 'YES'
elif self.CHECK == CHECK_REGULAR:
env['PYDEVD_USE_CYTHON'] = 'NO'
env['PYDEVD_USE_FRAME_EVAL'] = 'NO'
else:
raise AssertionError("Don't know what to check.")
return env
debugger_unittest.AbstractWriterThread.get_pydevd_file # overrides
def get_pydevd_file(self):
if self.CHECK == CHECK_BASELINE:
return os.path.abspath(os.path.join(r'X:\PyDev.Debugger.baseline', 'pydevd.py'))
dirname = os.path.dirname(__file__)
dirname = os.path.dirname(dirname)
return os.path.abspath(os.path.join(dirname, 'pydevd.py'))
class CheckDebuggerPerformance(debugger_unittest.DebuggerRunner):
def get_command_line(self):
return [sys.executable]
def _get_time_from_result(self, stdout):
match = re.search(r'TotalTime>>((\d|\.)+)<<', stdout)
time_taken = match.group(1)
return float(time_taken)
def obtain_results(self, benchmark_name, filename):
class PerformanceCheck(PerformanceWriterThread):
TEST_FILE = debugger_unittest._get_debugger_test_file(filename)
BENCHMARK_NAME = benchmark_name
writer_thread_class = PerformanceCheck
runs = RUNS
all_times = []
for _ in range(runs):
stdout_ref = []
def store_stdout(stdout, stderr):
stdout_ref.append(stdout)
with self.check_case(writer_thread_class) as writer:
writer.additional_output_checks = store_stdout
yield writer
assert len(stdout_ref) == 1
all_times.append(self._get_time_from_result(stdout_ref[0]))
print('partial for: %s: %.3fs' % (writer_thread_class.BENCHMARK_NAME, all_times[-1]))
if len(all_times) > 3:
all_times.remove(min(all_times))
all_times.remove(max(all_times))
time_when_debugged = sum(all_times) / float(len(all_times))
args = self.get_command_line()
args.append(writer_thread_class.TEST_FILE)
# regular_time = self._get_time_from_result(self.run_process(args, writer_thread=None))
# simple_trace_time = self._get_time_from_result(self.run_process(args+['--regular-trace'], writer_thread=None))
if 'SPEEDTIN_AUTHORIZATION_KEY' in os.environ:
SPEEDTIN_AUTHORIZATION_KEY = os.environ['SPEEDTIN_AUTHORIZATION_KEY']
# sys.path.append(r'X:\speedtin\pyspeedtin')
import pyspeedtin # If the authorization key is there, pyspeedtin must be available
import pydevd
pydevd_cython_project_id, pydevd_pure_python_project_id = 6, 7
if writer_thread_class.CHECK == CHECK_BASELINE:
project_ids = (pydevd_cython_project_id, pydevd_pure_python_project_id)
elif writer_thread_class.CHECK == CHECK_REGULAR:
project_ids = (pydevd_pure_python_project_id,)
elif writer_thread_class.CHECK == CHECK_CYTHON:
project_ids = (pydevd_cython_project_id,)
else:
raise AssertionError('Wrong check: %s' % (writer_thread_class.CHECK))
for project_id in project_ids:
api = pyspeedtin.PySpeedTinApi(authorization_key=SPEEDTIN_AUTHORIZATION_KEY, project_id=project_id)
benchmark_name = writer_thread_class.BENCHMARK_NAME
if writer_thread_class.CHECK == CHECK_BASELINE:
version = '0.0.1_baseline'
return # No longer commit the baseline (it's immutable right now).
else:
version = pydevd.__version__,
commit_id, branch, commit_date = api.git_commit_id_branch_and_date_from_path(pydevd.__file__)
api.add_benchmark(benchmark_name)
api.add_measurement(
benchmark_name,
value=time_when_debugged,
version=version,
released=False,
branch=branch,
commit_id=commit_id,
commit_date=commit_date,
)
api.commit()
self.performance_msg = '%s: %.3fs ' % (writer_thread_class.BENCHMARK_NAME, time_when_debugged)
def method_calls_with_breakpoint(self):
for writer in self.obtain_results('method_calls_with_breakpoint', '_performance_1.py'):
writer.write_add_breakpoint(17, 'method')
writer.write_make_initial_run()
writer.finished_ok = True
return self.performance_msg
def method_calls_without_breakpoint(self):
for writer in self.obtain_results('method_calls_without_breakpoint', '_performance_1.py'):
writer.write_make_initial_run()
writer.finished_ok = True
return self.performance_msg
def method_calls_with_step_over(self):
for writer in self.obtain_results('method_calls_with_step_over', '_performance_1.py'):
writer.write_add_breakpoint(26, None)
writer.write_make_initial_run()
hit = writer.wait_for_breakpoint_hit('111')
writer.write_step_over(hit.thread_id)
hit = writer.wait_for_breakpoint_hit('108')
writer.write_run_thread(hit.thread_id)
writer.finished_ok = True
return self.performance_msg
def method_calls_with_exception_breakpoint(self):
for writer in self.obtain_results('method_calls_with_exception_breakpoint', '_performance_1.py'):
writer.write_add_exception_breakpoint('ValueError')
writer.write_make_initial_run()
writer.finished_ok = True
return self.performance_msg
def global_scope_1_with_breakpoint(self):
for writer in self.obtain_results('global_scope_1_with_breakpoint', '_performance_2.py'):
writer.write_add_breakpoint(writer.get_line_index_with_content('Breakpoint here'), None)
writer.write_make_initial_run()
writer.finished_ok = True
return self.performance_msg
def global_scope_2_with_breakpoint(self):
for writer in self.obtain_results('global_scope_2_with_breakpoint', '_performance_3.py'):
writer.write_add_breakpoint(17, None)
writer.write_make_initial_run()
writer.finished_ok = True
return self.performance_msg
if __name__ == '__main__':
# Local times gotten (python 3.6)
# method_calls_with_breakpoint: 1.150s
# method_calls_without_breakpoint: 0.240s
# method_calls_with_step_over: 2.680s
# method_calls_with_exception_breakpoint: 0.235s
# global_scope_1_with_breakpoint: 4.249s
# global_scope_2_with_breakpoint: 2.807s
# Checking: cython
# method_calls_with_breakpoint: 0.526s
# method_calls_without_breakpoint: 0.130s
# method_calls_with_step_over: 1.133s
# method_calls_with_exception_breakpoint: 0.136s
# global_scope_1_with_breakpoint: 1.827s
# global_scope_2_with_breakpoint: 1.405s
# Checking: frame_eval
# method_calls_with_breakpoint: 0.133s
# method_calls_without_breakpoint: 0.128s
# method_calls_with_step_over: 0.130s
# method_calls_with_exception_breakpoint: 0.125s
# global_scope_1_with_breakpoint: 0.281s
# global_scope_2_with_breakpoint: 0.169s
# TotalTime for profile: 209.01s
debugger_unittest.SHOW_WRITES_AND_READS = False
debugger_unittest.SHOW_OTHER_DEBUG_INFO = False
debugger_unittest.SHOW_STDOUT = False
import time
start_time = time.time()
tmpdir = None
msgs = []
for check in (
# CHECK_BASELINE, -- Checks against the version checked out at X:\PyDev.Debugger.baseline.
CHECK_REGULAR,
CHECK_CYTHON,
CHECK_FRAME_EVAL,
):
PerformanceWriterThread.CHECK = check
msgs.append('Checking: %s' % (check,))
check_debugger_performance = CheckDebuggerPerformance(tmpdir)
msgs.append(check_debugger_performance.method_calls_with_breakpoint())
msgs.append(check_debugger_performance.method_calls_without_breakpoint())
msgs.append(check_debugger_performance.method_calls_with_step_over())
msgs.append(check_debugger_performance.method_calls_with_exception_breakpoint())
msgs.append(check_debugger_performance.global_scope_1_with_breakpoint())
msgs.append(check_debugger_performance.global_scope_2_with_breakpoint())
for msg in msgs:
print(msg)
print('TotalTime for profile: %.2fs' % (time.time() - start_time,))
@@ -0,0 +1,240 @@
# Based on: https://github.com/ESSS/pytest-regressions (License: MIT)
# Created copy because we need Python 2.6 which is not available on pytest-regressions.
# Note: only used for testing.
# encoding: UTF-8
import difflib
import pytest
import sys
from functools import partial
if sys.version_info[0] <= 2:
from pathlib2 import Path
else:
from pathlib import Path
FORCE_REGEN = False
@pytest.fixture
def original_datadir(request):
# Method from: https://github.com/gabrielcnr/pytest-datadir
# License: MIT
import os.path
return Path(os.path.splitext(request.module.__file__)[0])
@pytest.fixture
def datadir(original_datadir, tmpdir):
# Method from: https://github.com/gabrielcnr/pytest-datadir
# License: MIT
import shutil
result = Path(str(tmpdir.join(original_datadir.stem)))
if original_datadir.is_dir():
shutil.copytree(str(original_datadir), str(result))
else:
result.mkdir()
return result
@pytest.fixture
def data_regression(datadir, original_datadir, request):
return DataRegressionFixture(datadir, original_datadir, request)
def check_text_files(obtained_fn, expected_fn, fix_callback=lambda x: x, encoding=None):
"""
Compare two files contents. If the files differ, show the diff and write a nice HTML
diff file into the data directory.
:param Path obtained_fn: path to obtained file during current testing.
:param Path expected_fn: path to the expected file, obtained from previous testing.
:param str encoding: encoding used to open the files.
:param callable fix_callback:
A callback to "fix" the contents of the obtained (first) file.
This callback receives a list of strings (lines) and must also return a list of lines,
changed as needed.
The resulting lines will be used to compare with the contents of expected_fn.
"""
__tracebackhide__ = True
obtained_fn = Path(obtained_fn)
expected_fn = Path(expected_fn)
obtained_lines = fix_callback(obtained_fn.read_text(encoding=encoding).splitlines())
expected_lines = expected_fn.read_text(encoding=encoding).splitlines()
if obtained_lines != expected_lines:
diff_lines = list(difflib.unified_diff(expected_lines, obtained_lines))
if len(diff_lines) <= 500:
html_fn = obtained_fn.with_suffix(".diff.html")
try:
differ = difflib.HtmlDiff()
html_diff = differ.make_file(
fromlines=expected_lines,
fromdesc=expected_fn,
tolines=obtained_lines,
todesc=obtained_fn,
)
except Exception as e:
html_fn = "(failed to generate html diff: %s)" % e
else:
html_fn.write_text(html_diff, encoding="UTF-8")
diff = ["FILES DIFFER:", str(expected_fn), str(obtained_fn)]
diff += ["HTML DIFF: %s" % html_fn]
diff += diff_lines
raise AssertionError("\n".join(diff))
else:
# difflib has exponential scaling and for thousands of lines it starts to take minutes to render
# the HTML diff.
msg = [
"Files are different, but diff is too big (%s lines)" % (len(diff_lines),),
"- obtained: %s" % (obtained_fn,),
"- expected: %s" % (expected_fn,),
]
raise AssertionError("\n".join(msg))
def perform_regression_check(
datadir,
original_datadir,
request,
check_fn,
dump_fn,
extension,
basename=None,
fullpath=None,
obtained_filename=None,
dump_aux_fn=lambda filename: [],
):
"""
First run of this check will generate a expected file. Following attempts will always try to
match obtained files with that expected file.
:param Path datadir: Fixture embed_data.
:param Path original_datadir: Fixture embed_data.
:param SubRequest request: Pytest request object.
:param callable check_fn: A function that receives as arguments, respectively, absolute path to
obtained file and absolute path to expected file. It must assert if contents of file match.
Function can safely assume that obtained file is already dumped and only care about
comparison.
:param callable dump_fn: A function that receive an absolute file path as argument. Implementor
must dump file in this path.
:param callable dump_aux_fn: A function that receives the same file path as ``dump_fn``, but may
dump additional files to help diagnose this regression later (for example dumping image of
3d views and plots to compare later). Must return the list of file names written (used to display).
:param six.text_type extension: Extension of files compared by this check.
:param six.text_type obtained_filename: complete path to use to write the obtained file. By
default will prepend `.obtained` before the file extension.
..see: `data_regression.Check` for `basename` and `fullpath` arguments.
"""
import re
assert not (basename and fullpath), "pass either basename or fullpath, but not both"
__tracebackhide__ = True
if basename is None:
basename = re.sub(r"[\W]", "_", request.node.name)
if fullpath:
filename = source_filename = Path(fullpath)
else:
filename = datadir / (basename + extension)
source_filename = original_datadir / (basename + extension)
def make_location_message(banner, filename, aux_files):
msg = [banner, "- %s" % (filename,)]
if aux_files:
msg.append("Auxiliary:")
msg += ["- %s" % (x,) for x in aux_files]
return "\n".join(msg)
if not filename.is_file():
source_filename.parent.mkdir(parents=True, exist_ok=True)
dump_fn(source_filename)
aux_created = dump_aux_fn(source_filename)
msg = make_location_message(
"File not found in data directory, created:", source_filename, aux_created
)
pytest.fail(msg)
else:
if obtained_filename is None:
if fullpath:
obtained_filename = (datadir / basename).with_suffix(
".obtained" + extension
)
else:
obtained_filename = filename.with_suffix(".obtained" + extension)
dump_fn(obtained_filename)
try:
check_fn(obtained_filename, filename)
except AssertionError:
if FORCE_REGEN:
dump_fn(source_filename)
aux_created = dump_aux_fn(source_filename)
msg = make_location_message(
"Files differ and FORCE_REGEN set, regenerating file at:",
source_filename,
aux_created,
)
pytest.fail(msg)
else:
dump_aux_fn(obtained_filename)
raise
class DataRegressionFixture(object):
"""
Implementation of `data_regression` fixture.
"""
def __init__(self, datadir, original_datadir, request):
"""
:type datadir: Path
:type original_datadir: Path
:type request: FixtureRequest
"""
self.request = request
self.datadir = datadir
self.original_datadir = original_datadir
def check(self, data_dict, basename=None, fullpath=None):
"""
Checks the given dict against a previously recorded version, or generate a new file.
:param dict data_dict: any yaml serializable dict.
:param str basename: basename of the file to test/record. If not given the name
of the test is used.
Use either `basename` or `fullpath`.
:param str fullpath: complete path to use as a reference file. This option
will ignore ``datadir`` fixture when reading *expected* files but will still use it to
write *obtained* files. Useful if a reference file is located in the session data dir for example.
``basename`` and ``fullpath`` are exclusive.
"""
__tracebackhide__ = True
def dump(filename):
"""Dump dict contents to the given filename"""
import json
s = json.dumps(data_dict, sort_keys=True, indent=4)
if isinstance(s, bytes):
s = s.decode('utf-8')
s = u'\n'.join([line.rstrip() for line in s.splitlines()])
s = s.encode('utf-8')
with filename.open("wb") as f:
f.write(s)
perform_regression_check(
datadir=self.datadir,
original_datadir=self.original_datadir,
request=self.request,
check_fn=partial(check_text_files, encoding="UTF-8"),
dump_fn=dump,
extension=".json",
basename=basename,
fullpath=fullpath,
)
@@ -0,0 +1,6 @@
def call_me_back1(callback):
a = 'other'
callback()
return a
@@ -0,0 +1,331 @@
def foo():
a=1
b=2
c=3
d=4
e=5
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
if a == 1:
if b == 2:
if c == 3:
if d == 999:
x = 20
elif d == 998:
x = 40
elif d == 4:
if e != 5:
x = 20
else:
x = 50
assert x
@@ -0,0 +1,126 @@
from contextlib import contextmanager
def method1():
_a = 0
while _a < 2: # break while
_a += 1
def method2():
try:
raise AssertionError()
except: # break except
pass
@contextmanager
def ctx():
yield ''
def method3():
with ctx() as a: # break with
return a
def method4():
_a = 0
for i in range(2): # break for
_a = i
def method5():
try: # break try 1
_a = 10
finally:
_b = 10
def method6():
try:
_a = 10 # break try 2
finally:
_b = 10
def method7():
try:
_a = 10
finally:
_b = 10 # break finally 1
def method8():
try:
raise AssertionError()
except: # break except 2
_b = 10
finally:
_c = 20
def method9():
# As a note, Python 3.10 is eager to optimize this case and it duplicates the _c = 20
# in a codepath where the exception is raised and another where it's not raised.
# The frame eval mode must modify the bytecode so that both paths have the
# programmatic breakpoint added!
try:
_a = 10
except:
_b = 10
finally:_c = 20 # break finally 2
def method9a():
# Same as method9, but with exception raised (but handled).
try:
raise AssertionError()
except:
_b = 10
finally:_c = 20 # break finally 3
def method9b():
# Same as method9, but with exception raised (unhandled).
try:
try:
raise RuntimeError()
except AssertionError:
_b = 10
finally:_c = 20 # break finally 4
except:
pass
def method10():
_a = {
0: 0,
1: 1, # break in dict
2: 2,
}
def method11():
a = 11
if a == 10:
a = 20
else: a = 30 # break else
if __name__ == '__main__':
method1()
method2()
method3()
method4()
method5()
method6()
method7()
method8()
method9()
method9a()
method9b()
method10()
method11()
print('TEST SUCEEDED')
@@ -0,0 +1,268 @@
def foo():
a0 = 1
a1 = 1
a2 = 1
a3 = 1
a4 = 1
a5 = 1
a6 = 1
a7 = 1
a8 = 1
a9 = 1
a10 = 1
a11 = 1
a12 = 1
a13 = 1
a14 = 1
a15 = 1
a16 = 1
a17 = 1
a18 = 1
a19 = 1
a20 = 1
a21 = 1
a22 = 1
a23 = 1
a24 = 1
a25 = 1
a26 = 1
a27 = 1
a28 = 1
a29 = 1
a30 = 1
a31 = 1
a32 = 1
a33 = 1
a34 = 1
a35 = 1
a36 = 1
a37 = 1
a38 = 1
a39 = 1
a40 = 1
a41 = 1
a42 = 1
a43 = 1
a44 = 1
a45 = 1
a46 = 1
a47 = 1
a48 = 1
a49 = 1
a50 = 1
a51 = 1
a52 = 1
a53 = 1
a54 = 1
a55 = 1
a56 = 1
a57 = 1
a58 = 1
a59 = 1
a60 = 1
a61 = 1
a62 = 1
a63 = 1
a64 = 1
a65 = 1
a66 = 1
a67 = 1
a68 = 1
a69 = 1
a70 = 1
a71 = 1
a72 = 1
a73 = 1
a74 = 1
a75 = 1
a76 = 1
a77 = 1
a78 = 1
a79 = 1
a80 = 1
a81 = 1
a82 = 1
a83 = 1
a84 = 1
a85 = 1
a86 = 1
a87 = 1
a88 = 1
a89 = 1
a90 = 1
a91 = 1
a92 = 1
a93 = 1
a94 = 1
a95 = 1
a96 = 1
a97 = 1
a98 = 1
a99 = 1
a100 = 1
a101 = 1
a102 = 1
a103 = 1
a104 = 1
a105 = 1
a106 = 1
a107 = 1
a108 = 1
a109 = 1
a110 = 1
a111 = 1
a112 = 1
a113 = 1
a114 = 1
a115 = 1
a116 = 1
a117 = 1
a118 = 1
a119 = 1
a120 = 1
a121 = 1
a122 = 1
a123 = 1
a124 = 1
a125 = 1
a126 = 1
a127 = 1
a128 = 1
a129 = 1
a130 = 1
a131 = 1
a132 = 1
a133 = 1
a134 = 1
a135 = 1
a136 = 1
a137 = 1
a138 = 1
a139 = 1
a140 = 1
a141 = 1
a142 = 1
a143 = 1
a144 = 1
a145 = 1
a146 = 1
a147 = 1
a148 = 1
a149 = 1
a150 = 1
a151 = 1
a152 = 1
a153 = 1
a154 = 1
a155 = 1
a156 = 1
a157 = 1
a158 = 1
a159 = 1
a160 = 1
a161 = 1
a162 = 1
a163 = 1
a164 = 1
a165 = 1
a166 = 1
a167 = 1
a168 = 1
a169 = 1
a170 = 1
a171 = 1
a172 = 1
a173 = 1
a174 = 1
a175 = 1
a176 = 1
a177 = 1
a178 = 1
a179 = 1
a180 = 1
a181 = 1
a182 = 1
a183 = 1
a184 = 1
a185 = 1
a186 = 1
a187 = 1
a188 = 1
a189 = 1
a190 = 1
a191 = 1
a192 = 1
a193 = 1
a194 = 1
a195 = 1
a196 = 1
a197 = 1
a198 = 1
a199 = 1
a200 = 1
a201 = 1
a202 = 1
a203 = 1
a204 = 1
a205 = 1
a206 = 1
a207 = 1
a208 = 1
a209 = 1
a210 = 1
a211 = 1
a212 = 1
a213 = 1
a214 = 1
a215 = 1
a216 = 1
a217 = 1
a218 = 1
a219 = 1
a220 = 1
a221 = 1
a222 = 1
a223 = 1
a224 = 1
a225 = 1
a226 = 1
a227 = 1
a228 = 1
a229 = 1
a230 = 1
a231 = 1
a232 = 1
a233 = 1
a234 = 1
a235 = 1
a236 = 1
a237 = 1
a238 = 1
a239 = 1
a240 = 1
a241 = 1
a242 = 1
a243 = 1
a244 = 1
a245 = 1
a246 = 1
a247 = 1
a248 = 1
a249 = 1
a250 = 1
a251 = 1
a252 = 1
a253 = 1
a254 = 1
a255 = 1
a256 = 1
a257 = 1
a258 = 1
a259 = 1
b = a1 + a2
a260 = 1
a261 = 1
return b
@@ -0,0 +1,89 @@
def check_backtrack(x): # line 1
if not (x == 'a' # line 2
or x == 'c'): # line 3
pass # line 4
import re
import sys
en_lang_symbols = r'[^\w!@#$%\^-_+=|\}{][\"\';:?\/><.,&)(*\s`\u2019]'
en_words_basic = []
en_words = []
class Dummy:
non_en_words_limit = 3
@staticmethod
def fun(text):
words = tuple(w[0].lower() for w in re.finditer(r'[a-zA-Z]+', text))
non_en_pass = []
for i, word in enumerate(words):
non_en = []
if not (word in en_words_basic
or (word.endswith('s') and word[:-1] in en_words_basic)
or (word.endswith('ed') and word[:-2] in en_words_basic)
or (word.endswith('ing') and word[:-3] in en_words_basic)
or word in en_words
or (word.endswith('s') and word[:-1] in en_words)
or (word.endswith('ed') and word[:-2] in en_words)
or (word.endswith('ing') and word[:-3] in en_words)
):
non_en.append(word)
non_en_pass.append(word)
for j in range(1, Dummy.non_en_words_limit):
if i + j >= len(words):
break
word = words[i + j]
if (word in en_words_basic
or (word.endswith('s') and word[:-1] in en_words_basic)
or (word.endswith('ed') and word[:-2] in en_words_basic)
or (word.endswith('ing') and word[:-3] in en_words_basic)
or word in en_words
or (word.endswith('s') and word[:-1] in en_words)
or (word.endswith('ed') and word[:-2] in en_words)
or (word.endswith('ing') and word[:-3] in en_words)
):
break
else:
non_en.append(word)
non_en_pass.append(word)
def offset_overflow(stream=sys.stdout):
a = 1
b = 2
c = 3
a1 = 1 if a > 1 else 2
a2 = 1 if a > 1 else 2
a3 = 1 if a > 1 else 2
a4 = 1 if a > 1 else 2
a5 = 1 if a > 1 else 2
a6 = 1 if a > 1 else 2
a7 = 1 if a > 1 else 2
a8 = 1 if a > 1 else 2
a9 = 1 if a > 1 else 2
a10 = 1 if a > 1 else 2
a11 = 1 if a > 1 else 2
a12 = 1 if a > 1 else 2
a13 = 1 if a > 1 else 2
for i in range(1):
if a > 0:
stream.write("111\n")
# a = 1
else:
stream.write("222\n")
return b
def long_lines():
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
c = 1 if b > 1 else 2 if b > 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
d = 1 if c > 1 else 2 if c > 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
e = d + 1
return e
@@ -0,0 +1,22 @@
class A(object):
def __init__(self):
self.a = 10
class B(A):
def __init__(self):
super().__init__() # break here
assert self.a == 10
def method():
self.b = self.a
method()
assert self.b == 10
if __name__ == '__main__':
B()
print('TEST SUCEEDED')
@@ -0,0 +1,61 @@
import sys
import weakref
def set_up():
observable = Observable()
observer = Observer()
observable.add_observer(observer)
return observable
class Observable(object):
def __init__(self):
self.observers = []
def add_observer(self, observer):
sys.stdout.write( 'observer %s\n' % (observer,))
ref = weakref.ref(observer)
self.observers.append(ref)
sys.stdout.write('weakref: %s\n' % (ref(),))
def Notify(self):
for o in self.observers:
o = o()
try:
import gc
except ImportError:
o = None #some jython does not have gc, so, there's no sense testing this in it
else:
try:
gc.get_referrers(o)
except:
o = None #jython and ironpython do not have get_referrers
if o is not None:
sys.stdout.write('still observing %s\n' % (o,))
sys.stdout.write('number of referrers: %s\n' % len(gc.get_referrers(o)))
frame = gc.get_referrers(o)[0]
frame_referrers = gc.get_referrers(frame)
sys.stdout.write('frame referrer %s\n' % (frame_referrers,))
referrers1 = gc.get_referrers(frame_referrers[1])
sys.stdout.write('%s\n' % (referrers1,))
sys.stderr.write('TEST FAILED: The observer should have died, even when running in debug\n')
else:
sys.stdout.write('TEST SUCEEDED: observer died\n')
sys.stdout.flush()
sys.stderr.flush()
class Observer(object):
pass
def main():
observable = set_up()
observable.Notify()
if __name__ == '__main__':
main()
@@ -0,0 +1,43 @@
class TestProperty(object):
def __init__(self, name = "Default"):
self._x = None
self.name = name
def get_name(self):
return self.__name
def set_name(self, value):
self.__name = value
def del_name(self):
del self.__name
name = property(get_name, set_name, del_name, "name's docstring")
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
def main():
"""
"""
testObj = TestProperty()
testObj.x = 10
val = testObj.x
testObj.name = "Pydev"
debugType = testObj.name
print('TEST SUCEEDED!')
if __name__ == '__main__':
main()
@@ -0,0 +1,29 @@
class Car(object):
"""A car class"""
def __init__(self, model, make, color):
self.model = model
self.make = make
self.color = color
self.price = None
def get_price(self):
return self.price
def set_price(self, value):
self.price = value
availableCars = []
def main():
global availableCars
#Create a new car obj
carObj = Car("Maruti SX4", "2011", "Black")
carObj.set_price(950000) # Set price
# Add this to available cars
availableCars.append(carObj)
print('TEST SUCEEDED')
if __name__ == '__main__':
main()
@@ -0,0 +1,29 @@
class Car(object):
"""A car class"""
def __init__(self, model, make, color):
self.model = model
self.make = make
self.color = color
self.price = None
def get_price(self):
return self.price
def set_price(self, value):
self.price = value
availableCars = []
def main():
global availableCars
#Create a new car obj
carObj = Car("Maruti SX4", "2011", "Black")
carObj.set_price(950000) # Set price
# Add this to available cars
availableCars.append(carObj)
print('TEST SUCEEDED')
if __name__ == '__main__':
main()
@@ -0,0 +1 @@
f=lambda x: 'val=%s' % x
@@ -0,0 +1,12 @@
# this test requires numpy to be installed
import numpy
def main():
smallarray = numpy.arange(100) * 1 + 1j
bigarray = numpy.arange(100000).reshape((10,10000)) # 100 thousand
hugearray = numpy.arange(10000000) # 10 million
pass # location of breakpoint after all arrays defined
main()
print('TEST SUCEEDED')
@@ -0,0 +1,44 @@
def get_here():
a = 10
def foo(func):
return func
def m1(): # @DontTrace
get_here()
# @DontTrace
def m2():
get_here()
# @DontTrace
@foo
def m3():
get_here()
@foo
@foo
def m4(): # @DontTrace
get_here()
def main():
m1() # break1
m2() # break2
m3() # break3
m4() # break4
if __name__ == '__main__':
main()
print('TEST SUCEEDED')
@@ -0,0 +1,18 @@
def m1():
_a = 'm1' # break 1 here
def m2(): # @DontTrace
m1()
_a = 'm2'
def m3():
m2()
_a = 'm3' # break 2 here
if __name__ == '__main__':
m3()
print('TEST SUCEEDED')
@@ -0,0 +1,23 @@
import sys
def m2(a):
a = 10
b = 20 #Break here and set a = 40
c = 30
def function2():
print(a)
return a
def m1(a):
return m2(a)
if __name__ == '__main__':
found = m1(10)
if found == 40:
print('TEST SUCEEDED')
else:
raise AssertionError('Expected variable to be changed to 40. Found: %s' % (found,))
@@ -0,0 +1,10 @@
class A:
def __init__(self):
self.__var = 10
if __name__ == '__main__':
a = A()
print(a._A__var)
# Evaluate 'a.__var' should give a._A__var_
print('TEST SUCEEDED')
@@ -0,0 +1,24 @@
def Call4():
print('Start Call4')
print('End Call4')
def Call3():
print('Start Call3')
Call4()
print('End Call3')
def Call2():
print('Start Call2')
Call3()
print('End Call2 - a')
print('End Call2 - b')
def Call1():
print('Start Call1')
Call2()
print('End Call1')
if __name__ == '__main__':
Call1()
print('TEST SUCEEDED!')
@@ -0,0 +1,38 @@
import pydevd
import threading
original = pydevd.PyDB.notify_thread_created
found = set()
def new_notify_thread_created(self, thread_id, thread, *args, **kwargs):
found.add(thread)
return original(self, thread_id, thread, *args, **kwargs)
pydevd.PyDB.notify_thread_created = new_notify_thread_created
ok = []
class MyThread(threading.Thread):
def run(self):
if self not in found:
ok.append(False)
else:
ok.append(True)
if __name__ == '__main__':
threads = []
for i in range(15):
t = MyThread()
t.start()
threads.append(t)
for t in threads:
t.join()
assert len(ok) == len(threads)
assert all(ok), 'Expected all threads to be notified of their creation before starting to run. Found: %s' % (ok,)
found.clear()
print('TEST SUCEEDED')
@@ -0,0 +1,8 @@
import time
if __name__ == '__main__':
for i in range(15):
print('here')
time.sleep(.1)
print('TEST SUCEEDED')
@@ -0,0 +1,22 @@
import time
class ProceedContainer:
proceed = False
def exit_while_loop():
ProceedContainer.proceed = True
return 'ok'
def sleep():
while not ProceedContainer.proceed: # The debugger should change the proceed to True to exit the loop.
time.sleep(.1)
if __name__ == '__main__':
sleep()
print('TEST SUCEEDED')
@@ -0,0 +1,9 @@
def Call2():
print('Call2')
def Call1(a):
print('Call1')
if __name__ == '__main__':
Call1(Call2())
print('TEST SUCEEDED!')
@@ -0,0 +1,16 @@
def Method1():
print('m1')
def Method2():
print('m2 before')
Method1()
print('m2 after')
def Method3():
print('m3 before')
Method2()
print('m3 after')
if __name__ == '__main__':
Method3()
print('TEST SUCEEDED!')
@@ -0,0 +1,11 @@
def Call():
b = True
while b: # expected
# requested
pass # Note: until 3.10 a pass didn't generate a line event, but starting at 3.10, it does...
break
if __name__ == '__main__':
Call()
print('TEST SUCEEDED!')
@@ -0,0 +1,21 @@
import asyncio
import sys
async def gen():
f = sys._getframe()
for i in range(10):
await asyncio.sleep(.01)
assert f is sys._getframe()
yield i
async def run():
async for p in gen():
print(p)
if __name__ == "__main__":
loop = asyncio.get_event_loop_policy().get_event_loop()
loop.run_until_complete(run())
print('TEST SUCEEDED')
@@ -0,0 +1,37 @@
import asyncio
async def count():
print('enter count')
await asyncio.sleep(.001) # break count 1
await asyncio.sleep(.001) # break count 2
async def count2():
print('enter count 2')
await asyncio.sleep(.001)
await asyncio.sleep(.001)
async def count3():
print('enter count 3')
await asyncio.sleep(.001)
await asyncio.sleep(.001)
async def main():
await count() # break main
await count2() # step main
await count3()
if __name__ == "__main__":
if hasattr(asyncio, 'run'):
print('using asyncio.run')
asyncio.run(main())
else:
print('using loop.run_until_complete')
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
print('TEST SUCEEDED!')
@@ -0,0 +1,47 @@
import time
import sys
try:
import _thread
except:
import thread as _thread
if __name__ == '__main__':
lock = _thread.allocate_lock()
initialized = [False]
print('Main thread ident should be: %s' % (_thread.get_ident()))
def new_thread_function():
sys.secondary_id = _thread.get_ident()
print('Secondary thread ident should be: %s' % (_thread.get_ident()))
wait = True
with lock:
initialized[0] = True
while wait:
time.sleep(.1) # break thread here
_thread.start_new_thread(new_thread_function, ())
wait = True
while not initialized[0]:
time.sleep(.1)
with lock: # It'll be here until the secondary thread finishes (i.e.: releases the lock).
pass
import threading # Note: only import after the attach.
curr_thread_ident = threading.current_thread().ident
if hasattr(threading, 'main_thread'):
main_thread_ident = threading.main_thread().ident
else:
# Python 2 does not have main_thread, but we can still get the reference.
main_thread_ident = threading._shutdown.im_self.ident
if curr_thread_ident != main_thread_ident:
raise AssertionError('Expected current thread ident (%s) to be the main thread ident (%s)' % (
curr_thread_ident, main_thread_ident))
print('TEST SUCEEDED')
@@ -0,0 +1,17 @@
import time
if __name__ == '__main__':
wait = True
while wait:
time.sleep(1) # break here
# Ok, if it got here things are looking good, let's just make
# sure that the threading module main thread has the correct ident.
import threading # Note: only import after the attach.
if hasattr(threading, 'main_thread'):
assert threading.current_thread().ident == threading.main_thread().ident
else:
# Python 2 does not have main_thread, but we can still get the reference.
assert threading.current_thread().ident == threading._shutdown.im_self.ident
print('TEST SUCEEDED')
@@ -0,0 +1,7 @@
def break_in_method():
breakpoint() # Builtin on Py3, but we provide a backport on Py2.
break_in_method()
print('TEST SUCEEDED')
@@ -0,0 +1,7 @@
import sys
def break_in_method():
sys.__breakpointhook__() # Builtin on Py3, but we provide a backport on Py2.
break_in_method()
print('TEST SUCEEDED')
@@ -0,0 +1,8 @@
def Call():
for i in range(10): # break here
last_i = i
if __name__ == '__main__':
Call()
print('TEST SUCEEDED!')
@@ -0,0 +1,15 @@
if __name__ == '__main__':
import os
import sys
port = int(sys.argv[1])
root_dirname = os.path.dirname(os.path.dirname(__file__))
if root_dirname not in sys.path:
sys.path.append(root_dirname)
import pydevd
print('before pydevd.settrace')
breakpoint(port=port)
print('after pydevd.settrace')
print('TEST SUCEEDED!')
@@ -0,0 +1,14 @@
if __name__ == '__main__':
import os
import sys
port = int(sys.argv[1])
root_dirname = os.path.dirname(os.path.dirname(__file__))
if root_dirname not in sys.path:
sys.path.append(root_dirname)
print('before pydevd.settrace')
breakpoint(port=port) # Set up through custom sitecustomize.py
print('after pydevd.settrace')
print('TEST SUCEEDED!')
@@ -0,0 +1,12 @@
def method():
_a = 1 # break 1
_a = 2
_a = 3 # break 2
if __name__ == '__main__':
for i in range(2):
method()
print('TEST SUCEEDED') # break 3
@@ -0,0 +1,100 @@
import threading, atexit, sys
from collections import namedtuple
import os.path
if sys.version_info[0] >= 3:
from _thread import start_new_thread
else:
from thread import start_new_thread
FrameInfo = namedtuple('FrameInfo', 'filename, name, f_trace')
def _atexit():
sys.stderr.flush()
sys.stdout.flush()
# Register the TEST SUCEEDED msg to the exit of the process.
atexit.register(_atexit)
def _iter_frame_info(frame):
while frame is not None:
yield FrameInfo(
os.path.basename(frame.f_code.co_filename),
frame.f_code.co_name,
frame.f_trace.__name__ if frame.f_trace is not None else "None"
)
frame = frame.f_back
def check_frame_info(expected):
found = list(_iter_frame_info(sys._getframe().f_back))
def fail():
raise AssertionError('Expected:\n%s\n\nFound:\n%s\n' % (
'\n'.join(str(x) for x in expected),
'\n'.join(str(x) for x in found)))
for found_info, expected_info in zip(found, expected):
if found_info.filename != expected_info.filename or found_info.name != expected_info.name:
fail()
for f_trace in expected_info.f_trace.split('|'):
if f_trace == found_info.f_trace:
break
else:
fail()
def thread_func():
check_frame_info([
FrameInfo(filename='_debugger_case_check_tracer.py', name='thread_func', f_trace='trace_exception'),
FrameInfo(filename='threading.py', name='run', f_trace='None'),
FrameInfo(filename='threading.py', name='_bootstrap_inner', f_trace='trace_unhandled_exceptions'),
FrameInfo(filename='threading.py', name='_bootstrap', f_trace='None'),
FrameInfo(filename='pydev_monkey.py', name='__call__', f_trace='None')
])
th = threading.Thread(target=thread_func)
th.daemon = True
th.start()
event = threading.Event()
def thread_func2():
try:
check_frame_info([
FrameInfo(filename='_debugger_case_check_tracer.py', name='thread_func2', f_trace='trace_exception'),
FrameInfo(filename='pydev_monkey.py', name='__call__', f_trace='trace_unhandled_exceptions')
])
finally:
event.set()
start_new_thread(thread_func2, ())
event.wait()
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.
import time
time.sleep(.3)
check_frame_info([
FrameInfo(filename='_debugger_case_check_tracer.py', name='<module>', f_trace='trace_exception'),
FrameInfo(filename='pydevd_runpy.py', name='_run_code', f_trace='None'),
FrameInfo(filename='pydevd_runpy.py', name='_run_module_code', f_trace='None'),
FrameInfo(filename='pydevd_runpy.py', name='run_path', f_trace='None'),
FrameInfo(filename='pydevd.py', name='_exec', f_trace='trace_unhandled_exceptions'),
FrameInfo(filename='pydevd.py', name='run', f_trace='trace_dispatch|None'),
FrameInfo(filename='pydevd.py', name='main', f_trace='trace_dispatch|None'),
FrameInfo(filename='pydevd.py', name='<module>', f_trace='trace_dispatch|None')
])
print('TEST SUCEEDED')
@@ -0,0 +1,23 @@
def method1():
yield
print('here') # Break here
if __name__ == '__main__':
# i.e.: make sure we create 2 frames with different frameIds.
it1 = iter(method1())
it2 = iter(method1())
next(it1) # resume first
next(it2) # resume second
try:
next(it1) # finish first
except StopIteration:
pass
try:
next(it2) # finish second
except StopIteration:
pass
print('TEST SUCEEDED')
@@ -0,0 +1,23 @@
import sys
from _pydevd_bundle.pydevd_custom_frames import add_custom_frame
import threading
def call1():
add_custom_frame(sys._getframe(), 'call1', threading.current_thread().ident)
def call2():
add_custom_frame(sys._getframe(), 'call2', threading.current_thread().ident)
def call3():
add_custom_frame(sys._getframe(), 'call3', threading.current_thread().ident)
if __name__ == '__main__':
call1() # break here
call2()
call3()
print('TEST SUCEEDED')
@@ -0,0 +1,14 @@
import pydevd
from _pydevd_bundle._debug_adapter import pydevd_schema
body = pydevd_schema.OutputEventBody('some output', 'my_category')
event = pydevd_schema.OutputEvent(body)
pydevd.send_json_message(event)
pydevd.send_json_message({
"type": "event",
"event": "output",
"body": {"output": "some output 2", "category": "my_category2"}
})
print('TEST SUCEEDED')
@@ -0,0 +1,72 @@
'''
The idea here is that a secondary thread does the processing of instructions,
so, when all threads are stopped, doing an evaluation for:
processor.process('xxx')
would be locked until secondary threads start running.
See: https://github.com/microsoft/debugpy/issues/157
'''
import threading
try:
from queue import Queue
except:
from Queue import Queue
class EchoThread(threading.Thread):
def __init__(self, queue):
threading.Thread.__init__(self)
self._queue = queue
self.started = threading.Event()
def run(self):
self.started.set()
while True:
obj = self._queue.get()
if obj == 'finish':
break
print('processed', obj.value)
obj.event.set() # Break here 2
class NotificationObject(object):
def __init__(self, value):
self.value = value
self.event = threading.Event()
class Processor(object):
def __init__(self, queue):
self._queue = queue
def process(self, i):
obj = NotificationObject(i)
self._queue.put(obj)
assert obj.event.wait()
def finish(self):
self._queue.put('finish')
def main():
queue = Queue()
echo_thread = EchoThread(queue)
processor = Processor(queue)
echo_thread.start()
echo_thread.started.wait()
processor.process(1) # Break here 1
processor.process(2)
processor.process(3)
processor.finish()
if __name__ == '__main__':
main()
print('TEST SUCEEDED!')
@@ -0,0 +1,11 @@
import pydevd
# Some hackery to get the PyDevJsonCommandProcessor which is not exposed.
try:
json_command_processor = pydevd.get_global_debugger().reader.process_net_command_json.__self__
except:
json_command_processor = pydevd.get_global_debugger().reader.process_net_command_json.im_self
print(json_command_processor._options.to_json())
print('TEST SUCEEDED!')
@@ -0,0 +1,16 @@
def method1(n):
if n <= 0:
return 0 # Break here
method2(n - 1)
def method2(n):
method1(n - 1)
if __name__ == '__main__':
try:
method1(100)
except:
pass # Don't let it print the exception (just deal with caught exceptions).
print('TEST SUCEEDED!')
@@ -0,0 +1,13 @@
class A():
def __init__(self):
self.var1 = 10
self.attr = {} # Break here
def __dir__(self):
return list(self.attr)
if __name__ == '__main__':
a = A()
print('TEST SUCEEDED!')
@@ -0,0 +1,3 @@
def call_me_back(callback):
if callable(callback):
callback()
@@ -0,0 +1,18 @@
import sys
import os
try:
from _debugger_case_dont_trace import call_me_back
except ImportError:
sys.path.append(os.path.dirname(__file__))
from _debugger_case_dont_trace import call_me_back
def my_callback():
print('trace me') # Break here
if __name__ == '__main__':
call_me_back(my_callback)
call_me_back(my_callback)
print('TEST SUCEEDED!')
@@ -0,0 +1,8 @@
def Call():
var_1 = 5
var_all = 1 # Break here
if __name__ == '__main__':
Call()
print('TEST SUCEEDED!')
@@ -0,0 +1 @@
# File empty. Output is in the extension itself
@@ -0,0 +1,22 @@
import sys
def method3():
raise IndexError('foo') # raise indexerror line
def method2():
return method3() # reraise on method2
def method1():
try:
method2() # handle on method1
except:
pass # Ok, handled
assert '__exception__' not in sys._getframe().f_locals
if __name__ == '__main__':
method1()
print('TEST SUCEEDED!')
@@ -0,0 +1,70 @@
'''
Things this test checks:
- frame.f_trace is None when there are only regular breakpoints.
- The no-op tracing function is set by default (otherwise when set tracing functions have no effect).
- When stepping in, frame.f_trace must be set by the frame eval.
- When stepping over/return, the frame.f_trace must not be set on intermediate callers.
TODO:
- When frame.f_trace is set to the default tracing function, it'll become None again in frame
eval mode if not stepping (if breakpoints weren't changed).
- The tracing function in the frames that deal with unhandled exceptions must be set when dealing
with unhandled exceptions.
- The tracing function in the frames that deal with unhandled exceptions must NOT be set when
NOT dealing with unhandled exceptions.
- If handled exceptions should be dealt with, the proper tracing should be set in frame.f_trace.
'''
import sys
from _pydevd_frame_eval import pydevd_frame_tracing
def check_with_no_trace():
if False:
print('break on check_with_trace')
frame = sys._getframe()
if frame.f_trace is not None:
raise AssertionError('Expected %s to be None' % (frame.f_trace,))
if sys.gettrace() is not pydevd_frame_tracing.dummy_tracing_holder.dummy_trace_func:
raise AssertionError('Expected %s to be dummy_trace_func' % (sys.gettrace(),))
def check_step_in_then_step_return():
frame = sys._getframe()
f_trace = frame.f_trace
if f_trace.__class__.__name__ != 'SafeCallWrapper':
raise AssertionError('Expected %s to be SafeCallWrapper' % (f_trace.__class__.__name__,))
check_with_no_trace()
def check_revert_to_dummy():
check_with_no_trace()
if __name__ == '__main__':
# Check how frame eval works.
if sys.version_info[0:2] < (3, 6):
raise AssertionError('Only available for Python 3.6 onwards. Found: %s' % (sys.version_info[0:1],))
check_with_no_trace() # break on global (step over)
check_step_in_then_step_return()
import pydevd_tracing
import pydevd
# This is what a remote attach would do (should revert to the frame eval mode).
pydevd_tracing.SetTrace(pydevd.get_global_debugger().trace_dispatch)
check_revert_to_dummy()
print('TEST SUCEEDED!')
@@ -0,0 +1,18 @@
def generator2():
for i in range(5):
yield i
def generator():
print('start') # break here
yield from generator2() # step 1
print('end') # step 2
if __name__ == '__main__':
for i in generator(): # generator return
print(i)
print('TEST SUCEEDED!') # step 3
@@ -0,0 +1,17 @@
def get_return():
return 10
def generator():
print('start') # break here
yield 10 # step 1
return \
get_return() # step 2
if __name__ == '__main__':
for i in generator(): # generator return
print(i)
print('TEST SUCEEDED!') # step 3
@@ -0,0 +1,13 @@
def generator():
print('start') # break here
yield 10 # step 1
print('end') # step 2
if __name__ == '__main__':
for i in generator(): # generator return
print(i)
print('TEST SUCEEDED!') # step 3
@@ -0,0 +1,16 @@
def generator2():
for i in range(4):
yield i
def generator():
a = 42 # break here
for x in generator2():
yield x
sum = 0
for i in generator():
sum += i
print('TEST SUCEEDED!')
@@ -0,0 +1,14 @@
def generator2():
yield from range(4)
def generator():
a = 42 # break here
yield from generator2()
sum = 0
for i in generator():
sum += i
print('TEST SUCEEDED!')
@@ -0,0 +1,13 @@
def generator():
yield 1 # stop 1
yield 2 # stop 4
def main():
for i in generator(): # stop 3
print(i) # stop 2
if __name__ == '__main__':
main()
print('TEST SUCEEDED!')
@@ -0,0 +1,26 @@
def method():
a = 1
print('call %s' % (a,))
def method2():
print('call %s' % (a,))
while a < 10:
a += 1
print('call %s' % (a,))
try:
if a < 0:
print('call %s' % (a,))
raise ValueError
else:
method2()
except ValueError:
pass
print('call %s' % (a,))
if __name__ == '__main__':
method()
print('TEST SUCEEDED!')
@@ -0,0 +1,21 @@
import threading
event_set = False
inner_started = False
def method():
global inner_started
inner_started = True
while not event_set:
import time
time.sleep(.1)
t = threading.Thread(target=method)
t.start()
while not inner_started:
import time
time.sleep(.1)
print('break here')
event_set = True
t.join()
print('TEST SUCEEDED!')
@@ -0,0 +1,60 @@
#!/usr/bin/env python
from gevent import monkey, sleep, threading as gevent_threading
import sys
if 'remote' in sys.argv:
import pydevd
if '--use-dap-mode' in sys.argv:
pydevd.config('http_json', 'debugpy-dap')
port = int(sys.argv[1])
print('before pydevd.settrace')
pydevd.settrace(host=('' if 'as-server' in sys.argv else '127.0.0.1'), port=port, suspend=False)
print('after pydevd.settrace')
monkey.patch_all()
import threading
called = []
class MyGreenThread2(threading.Thread):
def run(self):
for _i in range(3):
sleep()
class MyGreenletThread(threading.Thread):
def run(self):
for _i in range(5):
called.append(self.name) # break here
t1 = MyGreenThread2()
t1.start()
sleep()
if __name__ == '__main__':
t1 = MyGreenletThread()
t1.name = 't1'
t2 = MyGreenletThread()
t2.name = 't2'
if hasattr(gevent_threading, 'Thread'):
# Only available in newer versions of gevent.
assert isinstance(t1, gevent_threading.Thread)
assert isinstance(t2, gevent_threading.Thread)
t1.start()
t2.start()
for t1 in (t1, t2):
t1.join()
# With gevent it's always the same (gevent coroutine support makes thread
# switching serial).
expected = ['t1', 't2', 't1', 't2', 't1', 't2', 't1', 't2', 't1', 't2']
if called != expected:
raise AssertionError("Expected:\n%s\nFound:\n%s" % (expected, called))
print('TEST SUCEEDED')
@@ -0,0 +1,21 @@
import gevent
def foo():
print('Running in foo')
gevent.sleep(0)
print('Explicit context switch to foo again')
def bar():
print('Explicit context to bar')
gevent.sleep(0) # break here
print('Implicit context switch back to bar')
if __name__ == '__main__':
gevent.joinall([
gevent.spawn(foo),
gevent.spawn(bar),
])
print('TEST SUCEEDED')
@@ -0,0 +1,16 @@
from gevent import monkey
monkey.patch_all()
import subprocess
import sys
import os
if __name__ == "__main__":
if '-foo' in sys.argv:
print('foo called')
else:
if os.environ.get('CALL_PYTHON_SUB') == '1':
assert 'foo called' in subprocess.check_output([sys.executable, __file__, '-foo']).decode('utf-8')
else:
subprocess.check_output("tput -T xterm-256color bold".split())
print('TEST SUCEEDED')
@@ -0,0 +1,12 @@
in_global_scope = 'in_global_scope_value'
class SomeClass(object):
def method(self):
print('breakpoint here')
if __name__ == '__main__':
SomeClass().method()
print('TEST SUCEEDED')
@@ -0,0 +1,26 @@
class LoopHolder:
@staticmethod
def gui_loop():
print('gui_loop() called')
def call_method():
from _pydevd_bundle.pydevd_constants import get_global_debugger
py_db = get_global_debugger()
# Check state prior to breaking
assert not py_db.gui_in_use
assert py_db._installed_gui_support
assert py_db._gui_event_loop == '__main__.LoopHolder.gui_loop'
print('break here')
assert py_db.gui_in_use
assert py_db._installed_gui_support
assert py_db._gui_event_loop == '__main__.LoopHolder.gui_loop'
if __name__ == '__main__':
call_method()
print('TEST SUCEEDED!')
@@ -0,0 +1,51 @@
def call_method():
from _pydevd_bundle.pydevd_constants import get_global_debugger
py_db = get_global_debugger()
# Check state prior to breaking
assert not py_db.gui_in_use
assert py_db._installed_gui_support
assert py_db._gui_event_loop == 'qt5'
import os
import PySide2
from PySide2.QtCore import QTimer
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', 'platforms')
if os.path.exists(plugin_path):
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path
from PySide2 import QtWidgets
app = QtWidgets.QApplication([])
def on_timeout():
print('on_timeout() called')
print_timer = QTimer()
print_timer.timeout.connect(on_timeout)
print_timer.setInterval(100)
print_timer.start()
def on_break():
print('break here')
app.quit()
break_on_timer = QTimer()
break_on_timer.timeout.connect(on_break)
break_on_timer.setSingleShot(True)
break_on_timer.setInterval(50)
break_on_timer.start()
app.exec_() # Run forever until app.quit()
assert py_db.gui_in_use
assert py_db._installed_gui_support
assert py_db._gui_event_loop == 'qt5'
if __name__ == '__main__':
call_method()
print('TEST SUCEEDED!')
@@ -0,0 +1,10 @@
class MyClass(object):
def __getattribute__(self, attr):
raise RuntimeError()
obj = MyClass()
if __name__ == '__main__':
print('TEST SUCEEDED') # break here

Some files were not shown because too many files have changed in this diff Show More