From 13f3f636162b8c8e17c2fe91a6899af8e757b75c Mon Sep 17 00:00:00 2001 From: fabioz Date: Thu, 4 Apr 2019 13:17:42 -0300 Subject: [PATCH] Django 2.2 tests and fixes for exception breakpoints in Django 1.7. Fixes #1298, #1294 --- .../pydevd/.travis_install_python_deps.sh | 4 +- .../pydevd/pydevd_plugins/django_debug.py | 35 +++++++++--- .../pydevd/tests_python/debugger_fixtures.py | 23 +++++++- .../my_app/templates/my_app/inherited.html | 5 ++ .../my_app/templates/my_app/inherits.html | 2 + .../templates/my_app/template_error.html | 13 +++++ .../templates/my_app/template_error2.html | 6 ++ .../my_django_proj_17/my_app/urls.py | 5 +- .../my_django_proj_17/my_app/views.py | 28 +++++++++- .../my_app/templates/my_app/inherited.html | 5 ++ .../my_app/templates/my_app/inherits.html | 2 + .../my_django_proj_21/my_app/urls.py | 1 + .../my_django_proj_21/my_app/views.py | 6 ++ .../pydevd/tests_python/test_debugger.py | 55 +++++++++---------- 14 files changed, 144 insertions(+), 46 deletions(-) create mode 100644 src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/inherited.html create mode 100644 src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/inherits.html create mode 100644 src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/template_error.html create mode 100644 src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/template_error2.html create mode 100644 src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/inherited.html create mode 100644 src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/inherits.html diff --git a/src/ptvsd/_vendored/pydevd/.travis_install_python_deps.sh b/src/ptvsd/_vendored/pydevd/.travis_install_python_deps.sh index eb10ba335..1e19fade1 100644 --- a/src/ptvsd/_vendored/pydevd/.travis_install_python_deps.sh +++ b/src/ptvsd/_vendored/pydevd/.travis_install_python_deps.sh @@ -29,13 +29,13 @@ fi if [ "$PYDEVD_PYTHON_VERSION" = "3.6" ]; then conda install --yes pyqt=5 gevent - pip install "django>=2.1,<2.2" + pip install "django>=2.2,<2.3" fi if [ "$PYDEVD_PYTHON_VERSION" = "3.7" ]; then conda install --yes pyqt=5 matplotlib # Note: track the latest django - pip install "django>=2.1,<2.2" + pip install "django" fi pip install untangle diff --git a/src/ptvsd/_vendored/pydevd/pydevd_plugins/django_debug.py b/src/ptvsd/_vendored/pydevd/pydevd_plugins/django_debug.py index 3e3ec0d4b..32ab05861 100644 --- a/src/ptvsd/_vendored/pydevd/pydevd_plugins/django_debug.py +++ b/src/ptvsd/_vendored/pydevd/pydevd_plugins/django_debug.py @@ -466,6 +466,21 @@ def suspend(plugin, main_debugger, thread, frame, bp_type): return None +def _get_filename_from_origin_in_parent_frame_locals(frame, parent_frame_name): + filename = None + parent_frame = frame + while parent_frame.f_code.co_name != parent_frame_name: + parent_frame = parent_frame.f_back + + origin = None + if parent_frame is not None: + origin = parent_frame.f_locals.get('origin') + + if hasattr(origin, 'name') and origin.name is not None: + filename = normcase(_convert_to_str(origin.name)) + return filename + + def exception_break(plugin, main_debugger, pydb_frame, frame, args, arg): main_debugger = args[0] thread = args[3] @@ -479,19 +494,21 @@ def exception_break(plugin, main_debugger, pydb_frame, frame, args, arg): # In this case we don't actually have a regular render frame with the context # (we didn't really get to that point). token = getattr(value, 'token', None) + + if token is None: + # Django 1.7 does not have token in exception. Try to get it from locals. + token = frame.f_locals.get('token') + lineno = getattr(token, 'lineno', None) + filename = None if lineno is not None: - get_template_frame = frame - while get_template_frame.f_code.co_name != 'get_template': - get_template_frame = get_template_frame.f_back - - origin = None - if get_template_frame is not None: - origin = get_template_frame.f_locals.get('origin') + filename = _get_filename_from_origin_in_parent_frame_locals(frame, 'get_template') - if hasattr(origin, 'name') and origin.name is not None: - filename = normcase(_convert_to_str(origin.name)) + if filename is None: + # Django 1.7 does not have origin in get_template. Try to get it from + # load_template. + filename = _get_filename_from_origin_in_parent_frame_locals(frame, 'load_template') if filename is not None and lineno is not None: syntax_error_frame = DjangoTemplateSyntaxErrorFrame( diff --git a/src/ptvsd/_vendored/pydevd/tests_python/debugger_fixtures.py b/src/ptvsd/_vendored/pydevd/tests_python/debugger_fixtures.py index b0468a5c3..728d38814 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/debugger_fixtures.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/debugger_fixtures.py @@ -10,6 +10,7 @@ CMD_ADD_EXCEPTION_BREAK import sys +import time def get_java_location(): @@ -134,6 +135,15 @@ def create_request_thread(self, url=''): class T(threading.Thread): + def wait_for_contents(self): + for _ in range(10): + if hasattr(self, 'contents'): + break + time.sleep(.3) + else: + raise AssertionError('Django did not return contents properly!') + return self.contents + def run(self): try: from urllib.request import urlopen @@ -203,6 +213,15 @@ def create_request_thread(self, uri): class T(threading.Thread): + def wait_for_contents(self): + for _ in range(10): + if hasattr(self, 'contents'): + break + time.sleep(.3) + else: + raise AssertionError('Django did not return contents properly!') + return self.contents + def run(self): try: from urllib.request import urlopen @@ -442,10 +461,10 @@ def test_file(self, **kwargs): version = [int(x) for x in django.get_version().split('.')][:2] if version == [1, 7]: django_folder = 'my_django_proj_17' - elif version == [2, 1]: + elif version in ([2, 1], [2, 2]): django_folder = 'my_django_proj_21' else: - raise AssertionError('Can only check django 1.7 and 2.1 right now. Found: %s' % (version,)) + raise AssertionError('Can only check django 1.7, 2.1 and 2.2 right now. Found: %s' % (version,)) WriterThread.DJANGO_FOLDER = django_folder for key, value in kwargs.items(): diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/inherited.html b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/inherited.html new file mode 100644 index 000000000..0105431c9 --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/inherited.html @@ -0,0 +1,5 @@ +{% if chat_mode %} + "chat_mode=True" +{% else %} + "chat_mode=False" +{% endif %} \ No newline at end of file diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/inherits.html b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/inherits.html new file mode 100644 index 000000000..8c7b9ab0a --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/inherits.html @@ -0,0 +1,2 @@ +{% include 'my_app/inherited.html' with chat_mode=True %} +{% include 'my_app/inherited.html' with chat_mode=False %} \ No newline at end of file diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/template_error.html b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/template_error.html new file mode 100644 index 000000000..4b2d70337 --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/template_error.html @@ -0,0 +1,13 @@ +{% if entries %} + +{% else %} +

No entries are available.

+{% endif %} \ No newline at end of file diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/template_error2.html b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/template_error2.html new file mode 100644 index 000000000..439f43223 --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/template_error2.html @@ -0,0 +1,6 @@ + + + + {% doesnotexist %} + + \ No newline at end of file diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/urls.py b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/urls.py index 32ce6af33..209ade309 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/urls.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/urls.py @@ -5,4 +5,7 @@ urlpatterns = [ url(r'^$', views.index, name='index'), url(r'^name$', views.get_name, name='name'), -] \ No newline at end of file + 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'), +] diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/views.py b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/views.py index 0e17f44fc..fbf527a12 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/views.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/views.py @@ -5,6 +5,7 @@ import sys from .forms import NameForm + class Entry(object): def __init__(self, key, val): @@ -17,6 +18,7 @@ def __unicode__(self): def __str__(self): return u'%s:%s' % (self.key, self.val) + def index(request): context = { 'entries': [Entry('v1', 'v1'), Entry('v2', 'v2')] @@ -24,6 +26,7 @@ def index(request): 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': @@ -38,6 +41,27 @@ def get_name(request): # if a GET (or any other method) we'll create a blank form else: - form = NameForm(data= {'your_name': 'unknown name'}) + 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 + - return render(request, 'my_app/name.html', {'form': form}) \ No newline at end of file +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 diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/inherited.html b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/inherited.html new file mode 100644 index 000000000..0105431c9 --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/inherited.html @@ -0,0 +1,5 @@ +{% if chat_mode %} + "chat_mode=True" +{% else %} + "chat_mode=False" +{% endif %} \ No newline at end of file diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/inherits.html b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/inherits.html new file mode 100644 index 000000000..8c7b9ab0a --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/inherits.html @@ -0,0 +1,2 @@ +{% include 'my_app/inherited.html' with chat_mode=True %} +{% include 'my_app/inherited.html' with chat_mode=False %} \ No newline at end of file diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/urls.py b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/urls.py index 4284ad832..d16734af3 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/urls.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/urls.py @@ -7,4 +7,5 @@ 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'), ] diff --git a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/views.py b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/views.py index b68358149..19289a60a 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/views.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/views.py @@ -66,3 +66,9 @@ def template_error2(request): 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 diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py index 1968b12ac..b45d0a18d 100644 --- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py +++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py @@ -870,15 +870,10 @@ def test_case_flask(case_setup_flask): writer.wait_for_vars(['Hello' in t.contents - assert 'Flask-Jinja-Test' in t.contents + assert 'Hello' in contents + assert 'Flask-Jinja-Test' in contents writer.finished_ok = True @@ -919,14 +914,9 @@ def get_environ(writer): writer.write_run_thread(hit.thread_id) - for _ in xrange(10): - if hasattr(t, 'contents'): - break - time.sleep(.3) - else: - raise AssertionError('Django did not return contents properly!') + contents = t.wait_for_contents() - contents = t.contents.replace(' ', '').replace('\r', '').replace('\n', '') + contents = contents.replace(' ', '').replace('\r', '').replace('\n', '') if contents != '': raise AssertionError('%s != ' % (contents,)) @@ -954,13 +944,28 @@ def test_case_django_b(case_setup_django): @pytest.mark.skipif(not TEST_DJANGO, reason='No django available') -@pytest.mark.parametrize("jmc", [False, True]) -def test_case_django_no_attribute_exception_breakpoint(case_setup_django, jmc): - django_version = [int(x) for x in django.get_version().split('.')][:2] +def test_case_django_template_inherits_no_exception(case_setup_django): + with case_setup_django.test_file(EXPECTED_RETURNCODE='any') as writer: + + # Check that it doesn't have issues with inherits + django exception breakpoints. + writer.write_add_exception_breakpoint_django() + + writer.write_make_initial_run() + + t = writer.create_request_thread('my_app/inherits') + time.sleep(5) # Give django some time to get to startup before requesting the page + t.start() + contents = t.wait_for_contents() + + contents = contents.replace(' ', '').replace('\r', '').replace('\n', '') + assert contents == '''"chat_mode=True""chat_mode=False"''' + + writer.finished_ok = True - if django_version < [2, 1]: - pytest.skip('Template exceptions only supporting Django 2.1 onwards.') +@pytest.mark.skipif(not TEST_DJANGO, reason='No django available') +@pytest.mark.parametrize("jmc", [False, True]) +def test_case_django_no_attribute_exception_breakpoint(case_setup_django, jmc): kwargs = {} if jmc: @@ -993,11 +998,6 @@ def get_environ(writer): @pytest.mark.skipif(not TEST_DJANGO, reason='No django available') def test_case_django_no_attribute_exception_breakpoint_and_regular_exceptions(case_setup_django): - django_version = [int(x) for x in django.get_version().split('.')][:2] - - if django_version < [2, 1]: - pytest.skip('Template exceptions only supporting Django 2.1 onwards.') - with case_setup_django.test_file(EXPECTED_RETURNCODE='any') as writer: writer.write_add_exception_breakpoint_django() @@ -1026,11 +1026,6 @@ def test_case_django_no_attribute_exception_breakpoint_and_regular_exceptions(ca @pytest.mark.skipif(not TEST_DJANGO, reason='No django available') @pytest.mark.parametrize("jmc", [False, True]) def test_case_django_invalid_template_exception_breakpoint(case_setup_django, jmc): - django_version = [int(x) for x in django.get_version().split('.')][:2] - - if django_version < [2, 1]: - pytest.skip('Template exceptions only supporting Django 2.1 onwards.') - kwargs = {} if jmc: