Handle jinja's UndefinedError in jinja.render

We can get UndefinedError when a new function got added to the jinja
env (and gets called from a template) and the user did update the
on-disk templates but not restart qutebrowser yet.

In this case, let's show a special error page to the user and tell them
to do :report in the unlikely case it's actually a bug.

Fixes #1362.
See #1360.
This commit is contained in:
Florian Bruhin 2016-03-25 23:57:29 +01:00
parent ebfe476319
commit 9edc5a665e
3 changed files with 51 additions and 4 deletions

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<!--
vim: ft=html fileencoding=utf-8 sts=4 sw=4 et:
-->
<html>
<head>
<meta charset="utf-8">
<title>Error while rendering HTML</title>
</head>
<body>
<h1>Error while rendering internal qutebrowser page</h1>
<p>There was an error while rendering {pagename}.</p>
<p>This most likely happened because you updated qutebrowser but didn't restart yet.</p>
<p>If you believe this isn't the case and this is a bug, please do :report.<p>
<h2>Traceback</h2>
<pre>{traceback}</pre>
</body>
</html>

View File

@ -21,10 +21,12 @@
import os import os
import os.path import os.path
import traceback
import jinja2 import jinja2
import jinja2.exceptions
from qutebrowser.utils import utils from qutebrowser.utils import utils, log
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
@ -74,8 +76,13 @@ def resource_url(path):
def render(template, **kwargs): def render(template, **kwargs):
"""Render the given template and pass the given arguments to it.""" """Render the given template and pass the given arguments to it."""
try:
return _env.get_template(template).render(**kwargs) return _env.get_template(template).render(**kwargs)
except jinja2.exceptions.UndefinedError:
log.misc.exception("UndefinedError while rendering " + template)
err_template = utils.read_file(os.path.join('html', 'undef_error.html'))
tb = traceback.format_exc()
return err_template.format(pagename=template, traceback=tb)
_env = jinja2.Environment(loader=Loader('html'), autoescape=_guess_autoescape) _env = jinja2.Environment(loader=Loader('html'), autoescape=_guess_autoescape)
_env.globals['resource_url'] = resource_url _env.globals['resource_url'] = resource_url

View File

@ -23,21 +23,27 @@ import os
import os.path import os.path
import pytest import pytest
import logging
import jinja2 import jinja2
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
from qutebrowser.utils import jinja from qutebrowser.utils import utils, jinja
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def patch_read_file(monkeypatch): def patch_read_file(monkeypatch):
"""pytest fixture to patch utils.read_file.""" """pytest fixture to patch utils.read_file."""
real_read_file = utils.read_file
def _read_file(path): def _read_file(path):
"""A read_file which returns a simple template if the path is right.""" """A read_file which returns a simple template if the path is right."""
if path == os.path.join('html', 'test.html'): if path == os.path.join('html', 'test.html'):
return """Hello {{var}}""" return """Hello {{var}}"""
elif path == os.path.join('html', 'test2.html'): elif path == os.path.join('html', 'test2.html'):
return """{{ resource_url('utils/testfile') }}""" return """{{ resource_url('utils/testfile') }}"""
elif path == os.path.join('html', 'undef.html'):
return """{{ does_not_exist() }}"""
elif path == os.path.join('html', 'undef_error.html'):
return real_read_file(path)
else: else:
raise IOError("Invalid path {}!".format(path)) raise IOError("Invalid path {}!".format(path))
@ -87,6 +93,18 @@ def test_utf8():
assert data == "Hello \u2603" assert data == "Hello \u2603"
def test_undefined_function(caplog):
"""Make sure we don't crash if an undefined function is called."""
with caplog.at_level(logging.ERROR):
data = jinja.render('undef.html')
assert 'There was an error while rendering undef.html' in data
assert "'does_not_exist' is undefined" in data
assert data.startswith('<!DOCTYPE html>')
assert len(caplog.records) == 1
assert caplog.records[0].msg == "UndefinedError while rendering undef.html"
@pytest.mark.parametrize('name, expected', [ @pytest.mark.parametrize('name, expected', [
(None, False), (None, False),
('foo', False), ('foo', False),