00001 from __future__ import absolute_import, division, with_statement
00002
00003 import os
00004 import traceback
00005
00006 from tornado.escape import utf8, native_str, to_unicode
00007 from tornado.template import Template, DictLoader, ParseError, Loader
00008 from tornado.testing import LogTrapTestCase
00009 from tornado.util import b, bytes_type, ObjectDict
00010
00011
00012 class TemplateTest(LogTrapTestCase):
00013 def test_simple(self):
00014 template = Template("Hello {{ name }}!")
00015 self.assertEqual(template.generate(name="Ben"),
00016 b("Hello Ben!"))
00017
00018 def test_bytes(self):
00019 template = Template("Hello {{ name }}!")
00020 self.assertEqual(template.generate(name=utf8("Ben")),
00021 b("Hello Ben!"))
00022
00023 def test_expressions(self):
00024 template = Template("2 + 2 = {{ 2 + 2 }}")
00025 self.assertEqual(template.generate(), b("2 + 2 = 4"))
00026
00027 def test_comment(self):
00028 template = Template("Hello{# TODO i18n #} {{ name }}!")
00029 self.assertEqual(template.generate(name=utf8("Ben")),
00030 b("Hello Ben!"))
00031
00032 def test_include(self):
00033 loader = DictLoader({
00034 "index.html": '{% include "header.html" %}\nbody text',
00035 "header.html": "header text",
00036 })
00037 self.assertEqual(loader.load("index.html").generate(),
00038 b("header text\nbody text"))
00039
00040 def test_extends(self):
00041 loader = DictLoader({
00042 "base.html": """\
00043 <title>{% block title %}default title{% end %}</title>
00044 <body>{% block body %}default body{% end %}</body>
00045 """,
00046 "page.html": """\
00047 {% extends "base.html" %}
00048 {% block title %}page title{% end %}
00049 {% block body %}page body{% end %}
00050 """,
00051 })
00052 self.assertEqual(loader.load("page.html").generate(),
00053 b("<title>page title</title>\n<body>page body</body>\n"))
00054
00055 def test_relative_load(self):
00056 loader = DictLoader({
00057 "a/1.html": "{% include '2.html' %}",
00058 "a/2.html": "{% include '../b/3.html' %}",
00059 "b/3.html": "ok",
00060 })
00061 self.assertEqual(loader.load("a/1.html").generate(),
00062 b("ok"))
00063
00064 def test_escaping(self):
00065 self.assertRaises(ParseError, lambda: Template("{{"))
00066 self.assertRaises(ParseError, lambda: Template("{%"))
00067 self.assertEqual(Template("{{!").generate(), b("{{"))
00068 self.assertEqual(Template("{%!").generate(), b("{%"))
00069 self.assertEqual(Template("{{ 'expr' }} {{!jquery expr}}").generate(),
00070 b("expr {{jquery expr}}"))
00071
00072 def test_unicode_template(self):
00073 template = Template(utf8(u"\u00e9"))
00074 self.assertEqual(template.generate(), utf8(u"\u00e9"))
00075
00076 def test_unicode_literal_expression(self):
00077
00078
00079
00080
00081 if str is unicode:
00082
00083
00084 template = Template(utf8(u'{{ "\u00e9" }}'))
00085 else:
00086 template = Template(utf8(u'{{ u"\u00e9" }}'))
00087 self.assertEqual(template.generate(), utf8(u"\u00e9"))
00088
00089 def test_custom_namespace(self):
00090 loader = DictLoader({"test.html": "{{ inc(5) }}"}, namespace={"inc": lambda x: x + 1})
00091 self.assertEqual(loader.load("test.html").generate(), b("6"))
00092
00093 def test_apply(self):
00094 def upper(s):
00095 return s.upper()
00096 template = Template(utf8("{% apply upper %}foo{% end %}"))
00097 self.assertEqual(template.generate(upper=upper), b("FOO"))
00098
00099 def test_if(self):
00100 template = Template(utf8("{% if x > 4 %}yes{% else %}no{% end %}"))
00101 self.assertEqual(template.generate(x=5), b("yes"))
00102 self.assertEqual(template.generate(x=3), b("no"))
00103
00104 def test_try(self):
00105 template = Template(utf8("""{% try %}
00106 try{% set y = 1/x %}
00107 {% except %}-except
00108 {% else %}-else
00109 {% finally %}-finally
00110 {% end %}"""))
00111 self.assertEqual(template.generate(x=1), b("\ntry\n-else\n-finally\n"))
00112 self.assertEqual(template.generate(x=0), b("\ntry-except\n-finally\n"))
00113
00114 def test_comment_directive(self):
00115 template = Template(utf8("{% comment blah blah %}foo"))
00116 self.assertEqual(template.generate(), b("foo"))
00117
00118
00119 class StackTraceTest(LogTrapTestCase):
00120 def test_error_line_number_expression(self):
00121 loader = DictLoader({"test.html": """one
00122 two{{1/0}}
00123 three
00124 """})
00125 try:
00126 loader.load("test.html").generate()
00127 except ZeroDivisionError:
00128 self.assertTrue("# test.html:2" in traceback.format_exc())
00129
00130 def test_error_line_number_directive(self):
00131 loader = DictLoader({"test.html": """one
00132 two{%if 1/0%}
00133 three{%end%}
00134 """})
00135 try:
00136 loader.load("test.html").generate()
00137 except ZeroDivisionError:
00138 self.assertTrue("# test.html:2" in traceback.format_exc())
00139
00140 def test_error_line_number_module(self):
00141 loader = DictLoader({
00142 "base.html": "{% module Template('sub.html') %}",
00143 "sub.html": "{{1/0}}",
00144 }, namespace={"_modules": ObjectDict({"Template": lambda path, **kwargs: loader.load(path).generate(**kwargs)})})
00145 try:
00146 loader.load("base.html").generate()
00147 except ZeroDivisionError:
00148 exc_stack = traceback.format_exc()
00149 self.assertTrue('# base.html:1' in exc_stack)
00150 self.assertTrue('# sub.html:1' in exc_stack)
00151
00152 def test_error_line_number_include(self):
00153 loader = DictLoader({
00154 "base.html": "{% include 'sub.html' %}",
00155 "sub.html": "{{1/0}}",
00156 })
00157 try:
00158 loader.load("base.html").generate()
00159 except ZeroDivisionError:
00160 self.assertTrue("# sub.html:1 (via base.html:1)" in
00161 traceback.format_exc())
00162
00163 def test_error_line_number_extends_base_error(self):
00164 loader = DictLoader({
00165 "base.html": "{{1/0}}",
00166 "sub.html": "{% extends 'base.html' %}",
00167 })
00168 try:
00169 loader.load("sub.html").generate()
00170 except ZeroDivisionError:
00171 exc_stack = traceback.format_exc()
00172 self.assertTrue("# base.html:1" in exc_stack)
00173
00174 def test_error_line_number_extends_sub_error(self):
00175 loader = DictLoader({
00176 "base.html": "{% block 'block' %}{% end %}",
00177 "sub.html": """
00178 {% extends 'base.html' %}
00179 {% block 'block' %}
00180 {{1/0}}
00181 {% end %}
00182 """})
00183 try:
00184 loader.load("sub.html").generate()
00185 except ZeroDivisionError:
00186 self.assertTrue("# sub.html:4 (via base.html:1)" in
00187 traceback.format_exc())
00188
00189 def test_multi_includes(self):
00190 loader = DictLoader({
00191 "a.html": "{% include 'b.html' %}",
00192 "b.html": "{% include 'c.html' %}",
00193 "c.html": "{{1/0}}",
00194 })
00195 try:
00196 loader.load("a.html").generate()
00197 except ZeroDivisionError:
00198 self.assertTrue("# c.html:1 (via b.html:1, a.html:1)" in
00199 traceback.format_exc())
00200
00201
00202 class AutoEscapeTest(LogTrapTestCase):
00203 def setUp(self):
00204 self.templates = {
00205 "escaped.html": "{% autoescape xhtml_escape %}{{ name }}",
00206 "unescaped.html": "{% autoescape None %}{{ name }}",
00207 "default.html": "{{ name }}",
00208
00209 "include.html": """\
00210 escaped: {% include 'escaped.html' %}
00211 unescaped: {% include 'unescaped.html' %}
00212 default: {% include 'default.html' %}
00213 """,
00214
00215 "escaped_block.html": """\
00216 {% autoescape xhtml_escape %}\
00217 {% block name %}base: {{ name }}{% end %}""",
00218 "unescaped_block.html": """\
00219 {% autoescape None %}\
00220 {% block name %}base: {{ name }}{% end %}""",
00221
00222
00223
00224 "escaped_extends_unescaped.html": """\
00225 {% autoescape xhtml_escape %}\
00226 {% extends "unescaped_block.html" %}""",
00227 "escaped_overrides_unescaped.html": """\
00228 {% autoescape xhtml_escape %}\
00229 {% extends "unescaped_block.html" %}\
00230 {% block name %}extended: {{ name }}{% end %}""",
00231 "unescaped_extends_escaped.html": """\
00232 {% autoescape None %}\
00233 {% extends "escaped_block.html" %}""",
00234 "unescaped_overrides_escaped.html": """\
00235 {% autoescape None %}\
00236 {% extends "escaped_block.html" %}\
00237 {% block name %}extended: {{ name }}{% end %}""",
00238
00239 "raw_expression.html": """\
00240 {% autoescape xhtml_escape %}\
00241 expr: {{ name }}
00242 raw: {% raw name %}""",
00243 }
00244
00245 def test_default_off(self):
00246 loader = DictLoader(self.templates, autoescape=None)
00247 name = "Bobby <table>s"
00248 self.assertEqual(loader.load("escaped.html").generate(name=name),
00249 b("Bobby <table>s"))
00250 self.assertEqual(loader.load("unescaped.html").generate(name=name),
00251 b("Bobby <table>s"))
00252 self.assertEqual(loader.load("default.html").generate(name=name),
00253 b("Bobby <table>s"))
00254
00255 self.assertEqual(loader.load("include.html").generate(name=name),
00256 b("escaped: Bobby <table>s\n"
00257 "unescaped: Bobby <table>s\n"
00258 "default: Bobby <table>s\n"))
00259
00260 def test_default_on(self):
00261 loader = DictLoader(self.templates, autoescape="xhtml_escape")
00262 name = "Bobby <table>s"
00263 self.assertEqual(loader.load("escaped.html").generate(name=name),
00264 b("Bobby <table>s"))
00265 self.assertEqual(loader.load("unescaped.html").generate(name=name),
00266 b("Bobby <table>s"))
00267 self.assertEqual(loader.load("default.html").generate(name=name),
00268 b("Bobby <table>s"))
00269
00270 self.assertEqual(loader.load("include.html").generate(name=name),
00271 b("escaped: Bobby <table>s\n"
00272 "unescaped: Bobby <table>s\n"
00273 "default: Bobby <table>s\n"))
00274
00275 def test_unextended_block(self):
00276 loader = DictLoader(self.templates)
00277 name = "<script>"
00278 self.assertEqual(loader.load("escaped_block.html").generate(name=name),
00279 b("base: <script>"))
00280 self.assertEqual(loader.load("unescaped_block.html").generate(name=name),
00281 b("base: <script>"))
00282
00283 def test_extended_block(self):
00284 loader = DictLoader(self.templates)
00285
00286 def render(name):
00287 return loader.load(name).generate(name="<script>")
00288 self.assertEqual(render("escaped_extends_unescaped.html"),
00289 b("base: <script>"))
00290 self.assertEqual(render("escaped_overrides_unescaped.html"),
00291 b("extended: <script>"))
00292
00293 self.assertEqual(render("unescaped_extends_escaped.html"),
00294 b("base: <script>"))
00295 self.assertEqual(render("unescaped_overrides_escaped.html"),
00296 b("extended: <script>"))
00297
00298 def test_raw_expression(self):
00299 loader = DictLoader(self.templates)
00300
00301 def render(name):
00302 return loader.load(name).generate(name='<>&"')
00303 self.assertEqual(render("raw_expression.html"),
00304 b("expr: <>&"\n"
00305 "raw: <>&\""))
00306
00307 def test_custom_escape(self):
00308 loader = DictLoader({"foo.py":
00309 "{% autoescape py_escape %}s = {{ name }}\n"})
00310
00311 def py_escape(s):
00312 self.assertEqual(type(s), bytes_type)
00313 return repr(native_str(s))
00314
00315 def render(template, name):
00316 return loader.load(template).generate(py_escape=py_escape,
00317 name=name)
00318 self.assertEqual(render("foo.py", "<html>"),
00319 b("s = '<html>'\n"))
00320 self.assertEqual(render("foo.py", "';sys.exit()"),
00321 b("""s = "';sys.exit()"\n"""))
00322 self.assertEqual(render("foo.py", ["not a string"]),
00323 b("""s = "['not a string']"\n"""))
00324
00325
00326 class TemplateLoaderTest(LogTrapTestCase):
00327 def setUp(self):
00328 self.loader = Loader(os.path.join(os.path.dirname(__file__), "templates"))
00329
00330 def test_utf8_in_file(self):
00331 tmpl = self.loader.load("utf8.html")
00332 result = tmpl.generate()
00333 self.assertEqual(to_unicode(result).strip(), u"H\u00e9llo")