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..022a4668c 100644 --- a/src/ptvsd/_vendored/pydevd/pydevd_plugins/django_debug.py +++ b/src/ptvsd/_vendored/pydevd/pydevd_plugins/django_debug.py @@ -358,6 +358,17 @@ def _is_django_variable_does_not_exist_exception_break_context(frame): name = None return name in ('_resolve_lookup', 'find_template') + +def _is_ignoring_failures(frame): + while frame is not None: + if frame.f_code.co_name == 'resolve': + ignore_failures = frame.f_locals.get('ignore_failures') + if ignore_failures: + return True + frame = frame.f_back + + return False + #======================================================================================================================= # Django Step Commands #======================================================================================================================= @@ -466,6 +477,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 +505,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( @@ -503,15 +531,16 @@ def exception_break(plugin, main_debugger, pydb_frame, frame, args, arg): elif exception.__name__ == 'VariableDoesNotExist': if _is_django_variable_does_not_exist_exception_break_context(frame): - render_frame = _find_django_render_frame(frame) - if render_frame: - suspend_frame = suspend_django( - main_debugger, thread, DjangoTemplateFrame(render_frame), CMD_ADD_EXCEPTION_BREAK) - if suspend_frame: - add_exception_to_frame(suspend_frame, (exception, value, trace)) - thread.additional_info.pydev_message = 'VariableDoesNotExist' - suspend_frame.f_back = frame - frame = suspend_frame - return True, frame + if not getattr(exception, 'silent_variable_failure', False) and not _is_ignoring_failures(frame): + render_frame = _find_django_render_frame(frame) + if render_frame: + suspend_frame = suspend_django( + main_debugger, thread, DjangoTemplateFrame(render_frame), CMD_ADD_EXCEPTION_BREAK) + if suspend_frame: + add_exception_to_frame(suspend_frame, (exception, value, trace)) + thread.additional_info.pydev_message = 'VariableDoesNotExist' + suspend_frame.f_back = frame + frame = suspend_frame + return True, frame return None 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/no_var_error.html b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/no_var_error.html new file mode 100644 index 000000000..bdb84e562 --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_17/my_app/templates/my_app/no_var_error.html @@ -0,0 +1,5 @@ +{% if pat.name %} + pat.name={{ pat.name }} +{% else %} + no_pat_name +{% 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_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..849aaf6b1 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,8 @@ 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'), + url(r'^no_var_error$', views.no_var_error, name='no_var_error'), +] 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..c15a9946e 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,33 @@ 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}) + - return render(request, 'my_app/name.html', {'form': form}) \ No newline at end of file +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 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/templates/my_app/no_var_error.html b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/no_var_error.html new file mode 100644 index 000000000..bdb84e562 --- /dev/null +++ b/src/ptvsd/_vendored/pydevd/tests_python/my_django_proj_21/my_app/templates/my_app/no_var_error.html @@ -0,0 +1,5 @@ +{% if pat.name %} + pat.name={{ pat.name }} +{% else %} + no_pat_name +{% endif %} \ 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..ada7ec44b 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,6 @@ 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'), ] 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..db7872d4b 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,15 @@ 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 + + +def no_var_error(request): + context = {} + ret = render(request, 'my_app/no_var_error.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..ad71b55bc 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,48 @@ 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 + + +@pytest.mark.skipif(not TEST_DJANGO, reason='No django available') +def test_case_django_no_var_error(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() - if django_version < [2, 1]: - pytest.skip('Template exceptions only supporting Django 2.1 onwards.') + t = writer.create_request_thread('my_app/no_var_error') + 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 == '''no_pat_name''' + + writer.finished_ok = True + + +@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 +1018,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 +1046,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: