Commit 4c7d9697 authored by Yuki Shiino's avatar Yuki Shiino Committed by Commit Bot

bind-gen: Improve error reporting and handling in MakoRenderer

Bug: 839389
Change-Id: Ic6935632890a2696427dd43a0f96d42468939f29
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1873806
Commit-Queue: Yuki Shiino <yukishiino@chromium.org>
Reviewed-by: default avatarHitoshi Yoshida <peria@chromium.org>
Cr-Commit-Position: refs/heads/master@{#708946}
parent cd984b40
......@@ -279,3 +279,23 @@ void blink::bindings::func(int arg1, int arg2) {
return var2;
}
""")
def test_template_error_handling(self):
renderer = MakoRenderer()
root = SymbolScopeNode(renderer=renderer)
root.append(
SymbolScopeNode([
# Have Mako raise a NameError.
TextNode("${unbound_symbol}"),
]))
with self.assertRaises(NameError):
root.render()
callers_on_error = list(renderer.callers_on_error)
self.assertEqual(len(callers_on_error), 3)
self.assertEqual(callers_on_error[0], root[0][0])
self.assertEqual(callers_on_error[1], root[0])
self.assertEqual(callers_on_error[2], root)
self.assertEqual(renderer.last_caller_on_error, root[0][0])
......@@ -2,6 +2,8 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import sys
import mako.lookup
import mako.template
......@@ -10,11 +12,15 @@ class MakoRenderer(object):
"""Represents a renderer object implemented with Mako templates."""
def __init__(self, template_dirs=None):
template_dirs = template_dirs or []
self._template_params = {
"strict_undefined": True,
}
self._template_lookup = mako.lookup.TemplateLookup(
directories=template_dirs)
directories=template_dirs, **self._template_params)
self._caller_stack = []
self._caller_stack_on_error = []
def render(self,
caller,
......@@ -47,8 +53,32 @@ class MakoRenderer(object):
if template_path is not None:
template = self._template_lookup.get_template(template_path)
elif template_text is not None:
template = mako.template.Template(text=template_text)
template = mako.template.Template(
text=template_text, **self._template_params)
text = template.render(**template_vars)
except:
# Print stacktrace of template rendering.
sys.stderr.write("\n")
sys.stderr.write("==== template rendering error ====\n")
sys.stderr.write(" * name: {}, type: {}\n".format(
_guess_caller_name(self.last_caller), type(self.last_caller)))
sys.stderr.write(" * depth: {}, module_id: {}\n".format(
len(self._caller_stack), template.module_id))
sys.stderr.write("---- template source ----\n")
sys.stderr.write(template.source)
# Save the error state at the deepest call.
current = self._caller_stack
on_error = self._caller_stack_on_error
if (len(current) <= len(on_error)
and all(current[i] == on_error[i]
for i in xrange(len(current)))):
pass # Error happened in a deeper caller.
else:
self._caller_stack_on_error = list(self._caller_stack)
raise
finally:
self._caller_stack.pop()
......@@ -66,3 +96,37 @@ class MakoRenderer(object):
def last_caller(self):
"""Returns the last caller in the call stack of this renderer."""
return self._caller_stack[-1]
@property
def callers_on_error(self):
"""
Returns the callers of this renderer in the order from the last caller
to the first caller at the moment when an exception was thrown.
"""
return reversed(self._caller_stack_on_error)
@property
def last_caller_on_error(self):
"""
Returns the deepest caller at the moment when an exception was thrown.
"""
return self._caller_stack_on_error[-1]
def _guess_caller_name(caller):
"""Returns the best-guessed name of |caller|."""
try:
# Outer CodeNode may have a binding to the caller.
for name, value in caller.outer.template_vars.iteritems():
if value is caller:
return name
try:
# Outer SequenceNode may contain the caller.
for index, value in enumerate(caller.outer, 1):
if value is caller:
return "{}-of-{}-in-seq".format(index, len(caller.outer))
except:
pass
return "<no name>"
except:
return "<unknown>"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment