123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- from __future__ import absolute_import, division, print_function
- import os
- import sys
- import traceback
- from tornado.escape import utf8, native_str, to_unicode
- from tornado.template import Template, DictLoader, ParseError, Loader
- from tornado.test.util import unittest
- from tornado.util import ObjectDict, unicode_type
- class TemplateTest(unittest.TestCase):
- def test_simple(self):
- template = Template("Hello {{ name }}!")
- self.assertEqual(template.generate(name="Ben"),
- b"Hello Ben!")
- def test_bytes(self):
- template = Template("Hello {{ name }}!")
- self.assertEqual(template.generate(name=utf8("Ben")),
- b"Hello Ben!")
- def test_expressions(self):
- template = Template("2 + 2 = {{ 2 + 2 }}")
- self.assertEqual(template.generate(), b"2 + 2 = 4")
- def test_comment(self):
- template = Template("Hello{# TODO i18n #} {{ name }}!")
- self.assertEqual(template.generate(name=utf8("Ben")),
- b"Hello Ben!")
- def test_include(self):
- loader = DictLoader({
- "index.html": '{% include "header.html" %}\nbody text',
- "header.html": "header text",
- })
- self.assertEqual(loader.load("index.html").generate(),
- b"header text\nbody text")
- def test_extends(self):
- loader = DictLoader({
- "base.html": """\
- <title>{% block title %}default title{% end %}</title>
- <body>{% block body %}default body{% end %}</body>
- """,
- "page.html": """\
- {% extends "base.html" %}
- {% block title %}page title{% end %}
- {% block body %}page body{% end %}
- """,
- })
- self.assertEqual(loader.load("page.html").generate(),
- b"<title>page title</title>\n<body>page body</body>\n")
- def test_relative_load(self):
- loader = DictLoader({
- "a/1.html": "{% include '2.html' %}",
- "a/2.html": "{% include '../b/3.html' %}",
- "b/3.html": "ok",
- })
- self.assertEqual(loader.load("a/1.html").generate(),
- b"ok")
- def test_escaping(self):
- self.assertRaises(ParseError, lambda: Template("{{"))
- self.assertRaises(ParseError, lambda: Template("{%"))
- self.assertEqual(Template("{{!").generate(), b"{{")
- self.assertEqual(Template("{%!").generate(), b"{%")
- self.assertEqual(Template("{#!").generate(), b"{#")
- self.assertEqual(Template("{{ 'expr' }} {{!jquery expr}}").generate(),
- b"expr {{jquery expr}}")
- def test_unicode_template(self):
- template = Template(utf8(u"\u00e9"))
- self.assertEqual(template.generate(), utf8(u"\u00e9"))
- def test_unicode_literal_expression(self):
- # Unicode literals should be usable in templates. Note that this
- # test simulates unicode characters appearing directly in the
- # template file (with utf8 encoding), i.e. \u escapes would not
- # be used in the template file itself.
- if str is unicode_type:
- # python 3 needs a different version of this test since
- # 2to3 doesn't run on template internals
- template = Template(utf8(u'{{ "\u00e9" }}'))
- else:
- template = Template(utf8(u'{{ u"\u00e9" }}'))
- self.assertEqual(template.generate(), utf8(u"\u00e9"))
- def test_custom_namespace(self):
- loader = DictLoader({"test.html": "{{ inc(5) }}"}, namespace={"inc": lambda x: x + 1})
- self.assertEqual(loader.load("test.html").generate(), b"6")
- def test_apply(self):
- def upper(s):
- return s.upper()
- template = Template(utf8("{% apply upper %}foo{% end %}"))
- self.assertEqual(template.generate(upper=upper), b"FOO")
- def test_unicode_apply(self):
- def upper(s):
- return to_unicode(s).upper()
- template = Template(utf8(u"{% apply upper %}foo \u00e9{% end %}"))
- self.assertEqual(template.generate(upper=upper), utf8(u"FOO \u00c9"))
- def test_bytes_apply(self):
- def upper(s):
- return utf8(to_unicode(s).upper())
- template = Template(utf8(u"{% apply upper %}foo \u00e9{% end %}"))
- self.assertEqual(template.generate(upper=upper), utf8(u"FOO \u00c9"))
- def test_if(self):
- template = Template(utf8("{% if x > 4 %}yes{% else %}no{% end %}"))
- self.assertEqual(template.generate(x=5), b"yes")
- self.assertEqual(template.generate(x=3), b"no")
- def test_if_empty_body(self):
- template = Template(utf8("{% if True %}{% else %}{% end %}"))
- self.assertEqual(template.generate(), b"")
- def test_try(self):
- template = Template(utf8("""{% try %}
- try{% set y = 1/x %}
- {% except %}-except
- {% else %}-else
- {% finally %}-finally
- {% end %}"""))
- self.assertEqual(template.generate(x=1), b"\ntry\n-else\n-finally\n")
- self.assertEqual(template.generate(x=0), b"\ntry-except\n-finally\n")
- def test_comment_directive(self):
- template = Template(utf8("{% comment blah blah %}foo"))
- self.assertEqual(template.generate(), b"foo")
- def test_break_continue(self):
- template = Template(utf8("""\
- {% for i in range(10) %}
- {% if i == 2 %}
- {% continue %}
- {% end %}
- {{ i }}
- {% if i == 6 %}
- {% break %}
- {% end %}
- {% end %}"""))
- result = template.generate()
- # remove extraneous whitespace
- result = b''.join(result.split())
- self.assertEqual(result, b"013456")
- def test_break_outside_loop(self):
- try:
- Template(utf8("{% break %}"))
- raise Exception("Did not get expected exception")
- except ParseError:
- pass
- def test_break_in_apply(self):
- # This test verifies current behavior, although of course it would
- # be nice if apply didn't cause seemingly unrelated breakage
- try:
- Template(utf8("{% for i in [] %}{% apply foo %}{% break %}{% end %}{% end %}"))
- raise Exception("Did not get expected exception")
- except ParseError:
- pass
- @unittest.skipIf(sys.version_info >= division.getMandatoryRelease(),
- 'no testable future imports')
- def test_no_inherit_future(self):
- # This file has from __future__ import division...
- self.assertEqual(1 / 2, 0.5)
- # ...but the template doesn't
- template = Template('{{ 1 / 2 }}')
- self.assertEqual(template.generate(), '0')
- def test_non_ascii_name(self):
- loader = DictLoader({u"t\u00e9st.html": "hello"})
- self.assertEqual(loader.load(u"t\u00e9st.html").generate(), b"hello")
- class StackTraceTest(unittest.TestCase):
- def test_error_line_number_expression(self):
- loader = DictLoader({"test.html": """one
- two{{1/0}}
- three
- """})
- try:
- loader.load("test.html").generate()
- self.fail("did not get expected exception")
- except ZeroDivisionError:
- self.assertTrue("# test.html:2" in traceback.format_exc())
- def test_error_line_number_directive(self):
- loader = DictLoader({"test.html": """one
- two{%if 1/0%}
- three{%end%}
- """})
- try:
- loader.load("test.html").generate()
- self.fail("did not get expected exception")
- except ZeroDivisionError:
- self.assertTrue("# test.html:2" in traceback.format_exc())
- def test_error_line_number_module(self):
- loader = None
- def load_generate(path, **kwargs):
- return loader.load(path).generate(**kwargs)
- loader = DictLoader({
- "base.html": "{% module Template('sub.html') %}",
- "sub.html": "{{1/0}}",
- }, namespace={"_tt_modules": ObjectDict(Template=load_generate)})
- try:
- loader.load("base.html").generate()
- self.fail("did not get expected exception")
- except ZeroDivisionError:
- exc_stack = traceback.format_exc()
- self.assertTrue('# base.html:1' in exc_stack)
- self.assertTrue('# sub.html:1' in exc_stack)
- def test_error_line_number_include(self):
- loader = DictLoader({
- "base.html": "{% include 'sub.html' %}",
- "sub.html": "{{1/0}}",
- })
- try:
- loader.load("base.html").generate()
- self.fail("did not get expected exception")
- except ZeroDivisionError:
- self.assertTrue("# sub.html:1 (via base.html:1)" in
- traceback.format_exc())
- def test_error_line_number_extends_base_error(self):
- loader = DictLoader({
- "base.html": "{{1/0}}",
- "sub.html": "{% extends 'base.html' %}",
- })
- try:
- loader.load("sub.html").generate()
- self.fail("did not get expected exception")
- except ZeroDivisionError:
- exc_stack = traceback.format_exc()
- self.assertTrue("# base.html:1" in exc_stack)
- def test_error_line_number_extends_sub_error(self):
- loader = DictLoader({
- "base.html": "{% block 'block' %}{% end %}",
- "sub.html": """
- {% extends 'base.html' %}
- {% block 'block' %}
- {{1/0}}
- {% end %}
- """})
- try:
- loader.load("sub.html").generate()
- self.fail("did not get expected exception")
- except ZeroDivisionError:
- self.assertTrue("# sub.html:4 (via base.html:1)" in
- traceback.format_exc())
- def test_multi_includes(self):
- loader = DictLoader({
- "a.html": "{% include 'b.html' %}",
- "b.html": "{% include 'c.html' %}",
- "c.html": "{{1/0}}",
- })
- try:
- loader.load("a.html").generate()
- self.fail("did not get expected exception")
- except ZeroDivisionError:
- self.assertTrue("# c.html:1 (via b.html:1, a.html:1)" in
- traceback.format_exc())
- class ParseErrorDetailTest(unittest.TestCase):
- def test_details(self):
- loader = DictLoader({
- "foo.html": "\n\n{{",
- })
- with self.assertRaises(ParseError) as cm:
- loader.load("foo.html")
- self.assertEqual("Missing end expression }} at foo.html:3",
- str(cm.exception))
- self.assertEqual("foo.html", cm.exception.filename)
- self.assertEqual(3, cm.exception.lineno)
- def test_custom_parse_error(self):
- # Make sure that ParseErrors remain compatible with their
- # pre-4.3 signature.
- self.assertEqual("asdf at None:0", str(ParseError("asdf")))
- class AutoEscapeTest(unittest.TestCase):
- def setUp(self):
- self.templates = {
- "escaped.html": "{% autoescape xhtml_escape %}{{ name }}",
- "unescaped.html": "{% autoescape None %}{{ name }}",
- "default.html": "{{ name }}",
- "include.html": """\
- escaped: {% include 'escaped.html' %}
- unescaped: {% include 'unescaped.html' %}
- default: {% include 'default.html' %}
- """,
- "escaped_block.html": """\
- {% autoescape xhtml_escape %}\
- {% block name %}base: {{ name }}{% end %}""",
- "unescaped_block.html": """\
- {% autoescape None %}\
- {% block name %}base: {{ name }}{% end %}""",
- # Extend a base template with different autoescape policy,
- # with and without overriding the base's blocks
- "escaped_extends_unescaped.html": """\
- {% autoescape xhtml_escape %}\
- {% extends "unescaped_block.html" %}""",
- "escaped_overrides_unescaped.html": """\
- {% autoescape xhtml_escape %}\
- {% extends "unescaped_block.html" %}\
- {% block name %}extended: {{ name }}{% end %}""",
- "unescaped_extends_escaped.html": """\
- {% autoescape None %}\
- {% extends "escaped_block.html" %}""",
- "unescaped_overrides_escaped.html": """\
- {% autoescape None %}\
- {% extends "escaped_block.html" %}\
- {% block name %}extended: {{ name }}{% end %}""",
- "raw_expression.html": """\
- {% autoescape xhtml_escape %}\
- expr: {{ name }}
- raw: {% raw name %}""",
- }
- def test_default_off(self):
- loader = DictLoader(self.templates, autoescape=None)
- name = "Bobby <table>s"
- self.assertEqual(loader.load("escaped.html").generate(name=name),
- b"Bobby <table>s")
- self.assertEqual(loader.load("unescaped.html").generate(name=name),
- b"Bobby <table>s")
- self.assertEqual(loader.load("default.html").generate(name=name),
- b"Bobby <table>s")
- self.assertEqual(loader.load("include.html").generate(name=name),
- b"escaped: Bobby <table>s\n"
- b"unescaped: Bobby <table>s\n"
- b"default: Bobby <table>s\n")
- def test_default_on(self):
- loader = DictLoader(self.templates, autoescape="xhtml_escape")
- name = "Bobby <table>s"
- self.assertEqual(loader.load("escaped.html").generate(name=name),
- b"Bobby <table>s")
- self.assertEqual(loader.load("unescaped.html").generate(name=name),
- b"Bobby <table>s")
- self.assertEqual(loader.load("default.html").generate(name=name),
- b"Bobby <table>s")
- self.assertEqual(loader.load("include.html").generate(name=name),
- b"escaped: Bobby <table>s\n"
- b"unescaped: Bobby <table>s\n"
- b"default: Bobby <table>s\n")
- def test_unextended_block(self):
- loader = DictLoader(self.templates)
- name = "<script>"
- self.assertEqual(loader.load("escaped_block.html").generate(name=name),
- b"base: <script>")
- self.assertEqual(loader.load("unescaped_block.html").generate(name=name),
- b"base: <script>")
- def test_extended_block(self):
- loader = DictLoader(self.templates)
- def render(name):
- return loader.load(name).generate(name="<script>")
- self.assertEqual(render("escaped_extends_unescaped.html"),
- b"base: <script>")
- self.assertEqual(render("escaped_overrides_unescaped.html"),
- b"extended: <script>")
- self.assertEqual(render("unescaped_extends_escaped.html"),
- b"base: <script>")
- self.assertEqual(render("unescaped_overrides_escaped.html"),
- b"extended: <script>")
- def test_raw_expression(self):
- loader = DictLoader(self.templates)
- def render(name):
- return loader.load(name).generate(name='<>&"')
- self.assertEqual(render("raw_expression.html"),
- b"expr: <>&"\n"
- b"raw: <>&\"")
- def test_custom_escape(self):
- loader = DictLoader({"foo.py":
- "{% autoescape py_escape %}s = {{ name }}\n"})
- def py_escape(s):
- self.assertEqual(type(s), bytes)
- return repr(native_str(s))
- def render(template, name):
- return loader.load(template).generate(py_escape=py_escape,
- name=name)
- self.assertEqual(render("foo.py", "<html>"),
- b"s = '<html>'\n")
- self.assertEqual(render("foo.py", "';sys.exit()"),
- b"""s = "';sys.exit()"\n""")
- self.assertEqual(render("foo.py", ["not a string"]),
- b"""s = "['not a string']"\n""")
- def test_manual_minimize_whitespace(self):
- # Whitespace including newlines is allowed within template tags
- # and directives, and this is one way to avoid long lines while
- # keeping extra whitespace out of the rendered output.
- loader = DictLoader({'foo.txt': """\
- {% for i in items
- %}{% if i > 0 %}, {% end %}{#
- #}{{i
- }}{% end
- %}""",
- })
- self.assertEqual(loader.load("foo.txt").generate(items=range(5)),
- b"0, 1, 2, 3, 4")
- def test_whitespace_by_filename(self):
- # Default whitespace handling depends on the template filename.
- loader = DictLoader({
- "foo.html": " \n\t\n asdf\t ",
- "bar.js": " \n\n\n\t qwer ",
- "baz.txt": "\t zxcv\n\n",
- "include.html": " {% include baz.txt %} \n ",
- "include.txt": "\t\t{% include foo.html %} ",
- })
- # HTML and JS files have whitespace compressed by default.
- self.assertEqual(loader.load("foo.html").generate(),
- b"\nasdf ")
- self.assertEqual(loader.load("bar.js").generate(),
- b"\nqwer ")
- # TXT files do not.
- self.assertEqual(loader.load("baz.txt").generate(),
- b"\t zxcv\n\n")
- # Each file maintains its own status even when included in
- # a file of the other type.
- self.assertEqual(loader.load("include.html").generate(),
- b" \t zxcv\n\n\n")
- self.assertEqual(loader.load("include.txt").generate(),
- b"\t\t\nasdf ")
- def test_whitespace_by_loader(self):
- templates = {
- "foo.html": "\t\tfoo\n\n",
- "bar.txt": "\t\tbar\n\n",
- }
- loader = DictLoader(templates, whitespace='all')
- self.assertEqual(loader.load("foo.html").generate(), b"\t\tfoo\n\n")
- self.assertEqual(loader.load("bar.txt").generate(), b"\t\tbar\n\n")
- loader = DictLoader(templates, whitespace='single')
- self.assertEqual(loader.load("foo.html").generate(), b" foo\n")
- self.assertEqual(loader.load("bar.txt").generate(), b" bar\n")
- loader = DictLoader(templates, whitespace='oneline')
- self.assertEqual(loader.load("foo.html").generate(), b" foo ")
- self.assertEqual(loader.load("bar.txt").generate(), b" bar ")
- def test_whitespace_directive(self):
- loader = DictLoader({
- "foo.html": """\
- {% whitespace oneline %}
- {% for i in range(3) %}
- {{ i }}
- {% end %}
- {% whitespace all %}
- pre\tformatted
- """})
- self.assertEqual(loader.load("foo.html").generate(),
- b" 0 1 2 \n pre\tformatted\n")
- class TemplateLoaderTest(unittest.TestCase):
- def setUp(self):
- self.loader = Loader(os.path.join(os.path.dirname(__file__), "templates"))
- def test_utf8_in_file(self):
- tmpl = self.loader.load("utf8.html")
- result = tmpl.generate()
- self.assertEqual(to_unicode(result).strip(), u"H\u00e9llo")
|