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