Rest of tests ported, just need to hook up doctests now.
authorArmin Ronacher <armin.ronacher@active-4.com>
Tue, 9 Feb 2010 20:14:16 +0000 (21:14 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Tue, 9 Feb 2010 20:14:16 +0000 (21:14 +0100)
--HG--
branch : trunk

jinja2/testsuite/__init__.py
jinja2/testsuite/api.py [new file with mode: 0644]
jinja2/testsuite/core_tags.py
jinja2/testsuite/debug.py [new file with mode: 0644]
jinja2/testsuite/ext.py
jinja2/testsuite/lexnparse.py
jinja2/testsuite/regression.py [new file with mode: 0644]
jinja2/testsuite/security.py
jinja2/testsuite/utils.py [new file with mode: 0644]

index bb156e890b1248b4f5925d2d9797de16970cd2a6..10320945edda97ab8519ba58c285ad3c27ae374a 100644 (file)
@@ -12,7 +12,9 @@
 """
 import os
 import sys
+import re
 import unittest
+from traceback import format_exception
 from jinja2 import loaders
 
 
@@ -42,10 +44,22 @@ class JinjaTestCase(unittest.TestCase):
     def assert_raises(self, *args, **kwargs):
         return self.assertRaises(*args, **kwargs)
 
+    def assert_traceback_matches(self, callback, expected_tb):
+        try:
+            callback()
+        except Exception, e:
+            tb = format_exception(*sys.exc_info())
+            if re.search(expected_tb.strip(), ''.join(tb)) is None:
+                raise self.fail('Traceback did not match:\n\n%s\nexpected:\n%s'
+                    % (''.join(tb), expected_tb))
+        else:
+            self.fail('Expected exception')
+
 
 def suite():
     from jinja2.testsuite import ext, filters, tests, core_tags, \
-         loader, inheritance, imports, lexnparse, security
+         loader, inheritance, imports, lexnparse, security, api, \
+         regression, debug
     suite = unittest.TestSuite()
     suite.addTest(ext.suite())
     suite.addTest(filters.suite())
@@ -56,4 +70,7 @@ def suite():
     suite.addTest(imports.suite())
     suite.addTest(lexnparse.suite())
     suite.addTest(security.suite())
+    suite.addTest(api.suite())
+    suite.addTest(regression.suite())
+    suite.addTest(debug.suite())
     return suite
diff --git a/jinja2/testsuite/api.py b/jinja2/testsuite/api.py
new file mode 100644 (file)
index 0000000..7e2d52f
--- /dev/null
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja2.testsuite.api
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Tests the public API and related stuff.
+
+    :copyright: (c) 2010 by the Jinja Team.
+    :license: BSD, see LICENSE for more details.
+"""
+import os
+import time
+import tempfile
+import unittest
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Environment, Undefined, DebugUndefined, \
+     StrictUndefined, UndefinedError, Template, meta, \
+     is_undefined
+from jinja2.utils import Cycler
+
+env = Environment()
+
+
+class ExtendedAPITestCase(JinjaTestCase):
+
+    def test_item_and_attribute(self):
+        from jinja2.sandbox import SandboxedEnvironment
+
+        for env in Environment(), SandboxedEnvironment():
+            tmpl = env.from_string('{{ foo.items() }}')
+            assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
+            tmpl = env.from_string('{{ foo|attr("items")() }}')
+            assert tmpl.render(foo={'items': 42}) == "[('items', 42)]"
+            tmpl = env.from_string('{{ foo["items"] }}')
+            assert tmpl.render(foo={'items': 42}) == '42'
+
+    def test_finalizer(self):
+        def finalize_none_empty(value):
+            if value is None:
+                value = u''
+            return value
+        env = Environment(finalize=finalize_none_empty)
+        tmpl = env.from_string('{% for item in seq %}|{{ item }}{% endfor %}')
+        assert tmpl.render(seq=(None, 1, "foo")) == '||1|foo'
+        tmpl = env.from_string('<{{ none }}>')
+        assert tmpl.render() == '<>'
+
+    def test_cycler(self):
+        items = 1, 2, 3
+        c = Cycler(*items)
+        for item in items + items:
+            assert c.current == item
+            assert c.next() == item
+        c.next()
+        assert c.current == 2
+        c.reset()
+        assert c.current == 1
+
+    def test_expressions(self):
+        expr = env.compile_expression("foo")
+        assert expr() is None
+        assert expr(foo=42) == 42
+        expr2 = env.compile_expression("foo", undefined_to_none=False)
+        assert is_undefined(expr2())
+
+        expr = env.compile_expression("42 + foo")
+        assert expr(foo=42) == 84
+
+
+class MetaTestCase(JinjaTestCase):
+
+    def test_find_undeclared_variables(self):
+        ast = env.parse('{% set foo = 42 %}{{ bar + foo }}')
+        x = meta.find_undeclared_variables(ast)
+        assert x == set(['bar'])
+
+        ast = env.parse('{% set foo = 42 %}{{ bar + foo }}'
+                        '{% macro meh(x) %}{{ x }}{% endmacro %}'
+                        '{% for item in seq %}{{ muh(item) + meh(seq) }}{% endfor %}')
+        x = meta.find_undeclared_variables(ast)
+        assert x == set(['bar', 'seq', 'muh'])
+
+    def test_find_refererenced_templates(self):
+        ast = env.parse('{% extends "layout.html" %}{% include helper %}')
+        i = meta.find_referenced_templates(ast)
+        assert i.next() == 'layout.html'
+        assert i.next() is None
+        assert list(i) == []
+
+        ast = env.parse('{% extends "layout.html" %}'
+                        '{% from "test.html" import a, b as c %}'
+                        '{% import "meh.html" as meh %}'
+                        '{% include "muh.html" %}')
+        i = meta.find_referenced_templates(ast)
+        assert list(i) == ['layout.html', 'test.html', 'meh.html', 'muh.html']
+
+    def test_find_included_templates(self):
+        ast = env.parse('{% include ["foo.html", "bar.html"] %}')
+        i = meta.find_referenced_templates(ast)
+        assert list(i) == ['foo.html', 'bar.html']
+
+        ast = env.parse('{% include ("foo.html", "bar.html") %}')
+        i = meta.find_referenced_templates(ast)
+        assert list(i) == ['foo.html', 'bar.html']
+
+        ast = env.parse('{% include ["foo.html", "bar.html", foo] %}')
+        i = meta.find_referenced_templates(ast)
+        assert list(i) == ['foo.html', 'bar.html', None]
+
+        ast = env.parse('{% include ("foo.html", "bar.html", foo) %}')
+        i = meta.find_referenced_templates(ast)
+        assert list(i) == ['foo.html', 'bar.html', None]
+
+
+class StreamingTestCase(JinjaTestCase):
+
+    def test_basic_streaming(self):
+        tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
+                               "}} - {{ item }}</li>{%- endfor %}</ul>")
+        stream = tmpl.stream(seq=range(4))
+        self.assert_equal(stream.next(), '<ul>')
+        self.assert_equal(stream.next(), '<li>1 - 0</li>')
+        self.assert_equal(stream.next(), '<li>2 - 1</li>')
+        self.assert_equal(stream.next(), '<li>3 - 2</li>')
+        self.assert_equal(stream.next(), '<li>4 - 3</li>')
+        self.assert_equal(stream.next(), '</ul>')
+
+    def test_buffered_streaming(self):
+        tmpl = env.from_string("<ul>{% for item in seq %}<li>{{ loop.index "
+                               "}} - {{ item }}</li>{%- endfor %}</ul>")
+        stream = tmpl.stream(seq=range(4))
+        stream.enable_buffering(size=3)
+        self.assert_equal(stream.next(), u'<ul><li>1 - 0</li><li>2 - 1</li>')
+        self.assert_equal(stream.next(), u'<li>3 - 2</li><li>4 - 3</li></ul>')
+
+    def test_streaming_behavior(self):
+        tmpl = env.from_string("")
+        stream = tmpl.stream()
+        assert not stream.buffered
+        stream.enable_buffering(20)
+        assert stream.buffered
+        stream.disable_buffering()
+        assert not stream.buffered
+
+
+class UndefinedTestCase(JinjaTestCase):
+
+    def test_default_undefined(self):
+        env = Environment(undefined=Undefined)
+        self.assert_equal(env.from_string('{{ missing }}').render(), u'')
+        self.assert_raises(UndefinedError,
+                           env.from_string('{{ missing.attribute }}').render)
+        self.assert_equal(env.from_string('{{ missing|list }}').render, '[]')
+        self.assert_equal(env.from_string('{{ missing is not defined }}').render, 'True')
+        self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42), '')
+        self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
+
+    def test_debug_undefined():
+        env = Environment(undefined=DebugUndefined)
+        self.assert_equal(env.from_string('{{ missing }}').render(), '{{ missing }}')
+        self.assert_raises(UndefinedError,
+                           env.from_string('{{ missing.attribute }}').render())
+        self.assert_equal(env.from_string('{{ missing|list }}').render(), '[]')
+        u'[]'
+        self.assert_equal(env.from_string('{{ missing is not defined }}').render, 'True')
+        self.assert_equal(env.from_string('{{ foo.missing }}').render(foo=42),
+                          u"{{ no such element: int['missing'] }}")
+        self.assert_equal(env.from_string('{{ not missing }}').render(), 'True')
+
+    def test_strict_undefined():
+        env = Environment(undefined=StrictUndefined)
+        self.assert_raises(UndefinedError, env.from_string('{{ missing }}').render)
+        self.assert_raises(UndefinedError, env.from_string('{{ missing.attribute }}').render)
+        self.assert_raises(UndefinedError, env.from_string('{{ missing|list }}').render)
+        self.assert_equal(env.from_string('{{ missing is not defined }}').render(), 'True')
+        self.assert_raises(UndefinedError, env.from_string('{{ foo.missing }}').render, foo=42)
+        self.assert_raises(UndefinedError, env.from_string('{{ not missing }}').render)
+
+    def test_indexing_gives_undefined(self):
+        t = Template("{{ var[42].foo }}")
+        assert_raises(UndefinedError, t.render, var=0)
+
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(ExtendedAPITestCase))
+    suite.addTest(unittest.makeSuite(MetaTestCase))
+    suite.addTest(unittest.makeSuite(StreamingTestCase))
+    return suite
index 4c9723a70eae69cc1dc310bbaf6c4e59cbbae1cc..9a6805c65744766a3bb77360094010f95293b806 100644 (file)
@@ -13,7 +13,8 @@ import unittest
 
 from jinja2.testsuite import JinjaTestCase
 
-from jinja2 import Environment, TemplateSyntaxError, UndefinedError
+from jinja2 import Environment, TemplateSyntaxError, UndefinedError, \
+     DictLoader
 
 env = Environment()
 
@@ -162,6 +163,11 @@ class ForLoopTestCase(JinjaTestCase):
         ''')
         assert t.render(foo=(1,)) == '...1......2...'
 
+    def test_unpacking(self):
+        tmpl = env.from_string('{% for a, b, c in [[1, 2, 3]] %}'
+            '{{ a }}|{{ b }}|{{ c }}{% endfor %}')
+        assert tmpl.render() == '1|2|3'
+
 
 class IfConditionTestCase(JinjaTestCase):
 
@@ -194,8 +200,83 @@ class IfConditionTestCase(JinjaTestCase):
         assert tmpl.render() == '1'
 
 
+class MacrosTestCase(JinjaTestCase):
+    env = Environment(trim_blocks=True)
+
+    def test_simple(self):
+        tmpl = self.env.from_string('''\
+{% macro say_hello(name) %}Hello {{ name }}!{% endmacro %}
+{{ say_hello('Peter') }}''')
+        assert tmpl.render() == 'Hello Peter!'
+
+    def test_scoping(self):
+        tmpl = self.env.from_string('''\
+{% macro level1(data1) %}
+{% macro level2(data2) %}{{ data1 }}|{{ data2 }}{% endmacro %}
+{{ level2('bar') }}{% endmacro %}
+{{ level1('foo') }}''')
+        print repr(tmpl.render())
+        assert tmpl.render() == 'foo|bar'
+
+    def test_arguments(self):
+        tmpl = self.env.from_string('''\
+{% macro m(a, b, c='c', d='d') %}{{ a }}|{{ b }}|{{ c }}|{{ d }}{% endmacro %}
+{{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}''')
+        print tmpl.render()
+        assert tmpl.render() == '||c|d|a||c|d|a|b|c|d|1|2|3|d'
+
+    def test_varargs(self):
+        tmpl = self.env.from_string('''\
+{% macro test() %}{{ varargs|join('|') }}{% endmacro %}\
+{{ test(1, 2, 3) }}''')
+        assert tmpl.render() == '1|2|3'
+
+    def test_simple_call(self):
+        tmpl = self.env.from_string('''\
+{% macro test() %}[[{{ caller() }}]]{% endmacro %}\
+{% call test() %}data{% endcall %}''')
+        assert tmpl.render() == '[[data]]'
+
+    def test_complex_call(self):
+        tmpl = self.env.from_string('''\
+{% macro test() %}[[{{ caller('data') }}]]{% endmacro %}\
+{% call(data) test() %}{{ data }}{% endcall %}''')
+        assert tmpl.render() == '[[data]]'
+
+    def test_caller_undefined(self):
+        tmpl = self.env.from_string('''\
+{% set caller = 42 %}\
+{% macro test() %}{{ caller is not defined }}{% endmacro %}\
+{{ test() }}''')
+        assert tmpl.render() == 'True'
+
+    def test_include(self):
+        self.env = Environment(loader=DictLoader({'include':
+            '{% macro test(foo) %}[{{ foo }}]{% endmacro %}'}))
+        tmpl = self.env.from_string('{% from "include" import test %}{{ test("foo") }}')
+        assert tmpl.render() == '[foo]'
+
+    def test_macro_api(self):
+        tmpl = self.env.from_string('{% macro foo(a, b) %}{% endmacro %}'
+                               '{% macro bar() %}{{ varargs }}{{ kwargs }}{% endmacro %}'
+                               '{% macro baz() %}{{ caller() }}{% endmacro %}')
+        assert tmpl.module.foo.arguments == ('a', 'b')
+        assert tmpl.module.foo.defaults == ()
+        assert tmpl.module.foo.name == 'foo'
+        assert not tmpl.module.foo.caller
+        assert not tmpl.module.foo.catch_kwargs
+        assert not tmpl.module.foo.catch_varargs
+        assert tmpl.module.bar.arguments == ()
+        assert tmpl.module.bar.defaults == ()
+        assert not tmpl.module.bar.caller
+        assert tmpl.module.bar.catch_kwargs
+        assert tmpl.module.bar.catch_varargs
+        assert tmpl.module.baz.caller
+
+
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(ForLoopTestCase))
     suite.addTest(unittest.makeSuite(IfConditionTestCase))
+    suite.addTest(unittest.makeSuite(MacrosTestCase))
     return suite
diff --git a/jinja2/testsuite/debug.py b/jinja2/testsuite/debug.py
new file mode 100644 (file)
index 0000000..d068e26
--- /dev/null
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja2.testsuite.debug
+    ~~~~~~~~~~~~~~~~~~~~~~
+
+    Tests the debug system.
+
+    :copyright: (c) 2010 by the Jinja Team.
+    :license: BSD, see LICENSE for more details.
+"""
+import unittest
+
+from jinja2.testsuite import JinjaTestCase, filesystem_loader
+
+from jinja2 import Environment, TemplateSyntaxError
+
+env = Environment(loader=filesystem_loader)
+
+
+class DebugTestCase(JinjaTestCase):
+
+    def test_runtime_error(self):
+        def test():
+            tmpl.render(fail=lambda: 1 / 0)
+        tmpl = env.get_template('broken.html')
+        self.assert_traceback_matches(test, r'''
+  File ".*?broken.html", line 2, in top-level template code
+    \{\{ fail\(\) \}\}
+  File ".*?debug.pyc?", line \d+, in <lambda>
+    tmpl\.render\(fail=lambda: 1 / 0\)
+ZeroDivisionError: integer division or modulo by zero
+''')
+
+    def test_syntax_error(self):
+        self.assert_traceback_matches(lambda: env.get_template('syntaxerror.html'), r'''
+  File ".*?syntaxerror.html", line 4, in template
+    \{% endif %\}
+TemplateSyntaxError: Encountered unknown tag 'endif'. Jinja was looking for the following tags: 'endfor' or 'else'. The innermost block that needs to be closed is 'for'.
+    ''')
+
+    def test_regular_syntax_error(self):
+        def test():
+            raise TemplateSyntaxError('wtf', 42)
+        self.assert_traceback_matches(test, r'''
+  File ".*debug.pyc?", line \d+, in test
+    raise TemplateSyntaxError\('wtf', 42\)
+TemplateSyntaxError: wtf
+  line 42''')
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(DebugTestCase))
+    return suite
index 57298ac6c395c0ff4898353778a9abc372434af4..3bb5276e3548f738fbc3b15800da5debb85da22f 100644 (file)
@@ -13,7 +13,8 @@ import unittest
 
 from jinja2.testsuite import JinjaTestCase, filesystem_loader
 
-from jinja2 import Environment, nodes
+from jinja2 import Environment, DictLoader, contextfunction, nodes
+from jinja2.exceptions import TemplateAssertionError
 from jinja2.ext import Extension
 from jinja2.lexer import Token, count_newlines
 
@@ -23,6 +24,53 @@ importable_object = 23
 _gettext_re = re.compile(r'_\((.*?)\)(?s)')
 
 
+templates = {
+    'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
+                   '{% block body %}{% endblock %}',
+    'child.html': '{% extends "master.html" %}{% block body %}'
+                  '{% trans %}watch out{% endtrans %}{% endblock %}',
+    'plural.html': '{% trans user_count %}One user online{% pluralize %}'
+                   '{{ user_count }} users online{% endtrans %}',
+    'stringformat.html': '{{ _("User: %d")|format(user_count) }}'
+}
+
+
+languages = {
+    'de': {
+        'missing':                      'fehlend',
+        'watch out':                    'pass auf',
+        'One user online':              'Ein Benutzer online',
+        '%(user_count)s users online':  '%(user_count)s Benutzer online',
+        'User: %d':                     'Benutzer: %d'
+    }
+}
+
+
+@contextfunction
+def gettext(context, string):
+    language = context.get('LANGUAGE', 'en')
+    return languages.get(language, {}).get(string, string)
+
+
+@contextfunction
+def ngettext(context, s, p, n):
+    language = context.get('LANGUAGE', 'en')
+    if n != 1:
+        return languages.get(language, {}).get(p, p)
+    return languages.get(language, {}).get(s, s)
+
+
+i18n_env = Environment(
+    loader=DictLoader(templates),
+    extensions=['jinja2.ext.i18n']
+)
+i18n_env.globals.update({
+    '_':            gettext,
+    'gettext':      gettext,
+    'ngettext':     ngettext
+})
+
+
 class TestExtension(Extension):
     tags = set(['test'])
     ext_attr = 42
@@ -150,7 +198,61 @@ class ExtensionsTestCase(JinjaTestCase):
         assert out == 'Foo BAR Baz'
 
 
+class InternationalizationTestCase(JinjaTestCase):
+
+    def test_trans(self):
+        tmpl = i18n_env.get_template('child.html')
+        assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
+
+    def test_trans_plural(self):
+        tmpl = i18n_env.get_template('plural.html')
+        assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
+        assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
+
+    def test_complex_plural(self):
+        tmpl = i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
+                                    'pluralize count %}{{ count }} items{% endtrans %}')
+        assert tmpl.render() == '2 items'
+        self.assert_raises(TemplateAssertionError, i18n_env.from_string,
+                           '{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
+
+    def test_trans_stringformatting(self):
+        tmpl = i18n_env.get_template('stringformat.html')
+        assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
+
+    def test_extract(self):
+        from jinja2.ext import babel_extract
+        from StringIO import StringIO
+        source = StringIO('''
+        {{ gettext('Hello World') }}
+        {% trans %}Hello World{% endtrans %}
+        {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
+        ''')
+        assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
+            (2, 'gettext', u'Hello World', []),
+            (3, 'gettext', u'Hello World', []),
+            (4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
+        ]
+
+    def test_comment_extract(self):
+        from jinja2.ext import babel_extract
+        from StringIO import StringIO
+        source = StringIO('''
+        {# trans first #}
+        {{ gettext('Hello World') }}
+        {% trans %}Hello World{% endtrans %}{# trans second #}
+        {#: third #}
+        {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
+        ''')
+        assert list(babel_extract(source, ('gettext', 'ngettext', '_'), ['trans', ':'], {})) == [
+            (3, 'gettext', u'Hello World', ['first']),
+            (4, 'gettext', u'Hello World', ['second']),
+            (6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), ['third'])
+        ]
+
+
 def suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(ExtensionsTestCase))
+    suite.addTest(unittest.makeSuite(InternationalizationTestCase))
     return suite
index 4a4336ec91d63e1bfd218917fdfe3b4b6177b003..84a2c7e21ead51928794c1cd05b170464838301b 100644 (file)
@@ -22,7 +22,7 @@ env = Environment()
 
 class LexerTestCase(JinjaTestCase):
 
-    def test_raw(self):
+    def test_raw1(self):
         tmpl = env.from_string('{% raw %}foo{% endraw %}|'
                                '{%raw%}{{ bar }}|{% baz %}{%       endraw    %}')
         assert tmpl.render() == 'foo|{{ bar }}|{% baz %}'
@@ -281,7 +281,6 @@ class SyntaxTestCase(JinjaTestCase):
             else:
                 env.from_string('foo(%s)' % sig)
 
-
     def test_tuple_expr(self):
         for tmpl in [
             '{{ () }}',
@@ -333,6 +332,27 @@ class SyntaxTestCase(JinjaTestCase):
         t = env.from_string('{{ foo[1, 2] }}')
         assert t.render(foo=Foo()) == u'(1, 2)'
 
+    def test_raw2(self):
+        tmpl = env.from_string('{% raw %}{{ FOO }} and {% BAR %}{% endraw %}')
+        assert tmpl.render() == '{{ FOO }} and {% BAR %}'
+
+    def test_const(self):
+        tmpl = env.from_string('{{ true }}|{{ false }}|{{ none }}|'
+                               '{{ none is defined }}|{{ missing is defined }}')
+        assert tmpl.render() == 'True|False|None|True|False'
+
+    def test_const_assign(self):
+        constass1 = '''{% set true = 42 %}'''
+        constass2 = '''{% for none in seq %}{% endfor %}'''
+        for tmpl in constass1, constass2:
+            self.assert_raises(TemplateSyntaxError, env.from_string, tmpl)
+
+    def test_localset(self):
+        tmpl = env.from_string('''{% set foo = 0 %}\
+{% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
+{{ foo }}''')
+        assert tmpl.render() == '0'
+
 
 def suite():
     suite = unittest.TestSuite()
diff --git a/jinja2/testsuite/regression.py b/jinja2/testsuite/regression.py
new file mode 100644 (file)
index 0000000..12e09ff
--- /dev/null
@@ -0,0 +1,245 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja2.testsuite.regression
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Tests corner cases and bugs.
+
+    :copyright: (c) 2010 by the Jinja Team.
+    :license: BSD, see LICENSE for more details.
+"""
+import os
+import time
+import tempfile
+import unittest
+
+from jinja2.testsuite import JinjaTestCase
+
+from jinja2 import Template, Environment, DictLoader, TemplateSyntaxError, \
+     TemplateNotFound, PrefixLoader
+
+env = Environment()
+
+
+class CornerTestCase(JinjaTestCase):
+
+    def test_assigned_scoping(self):
+        t = env.from_string('''
+        {%- for item in (1, 2, 3, 4) -%}
+            [{{ item }}]
+        {%- endfor %}
+        {{- item -}}
+        ''')
+        assert t.render(item=42) == '[1][2][3][4]42'
+
+        t = env.from_string('''
+        {%- for item in (1, 2, 3, 4) -%}
+            [{{ item }}]
+        {%- endfor %}
+        {%- set item = 42 %}
+        {{- item -}}
+        ''')
+        assert t.render() == '[1][2][3][4]42'
+
+        t = env.from_string('''
+        {%- set item = 42 %}
+        {%- for item in (1, 2, 3, 4) -%}
+            [{{ item }}]
+        {%- endfor %}
+        {{- item -}}
+        ''')
+        assert t.render() == '[1][2][3][4]42'
+
+    def test_closure_scoping(self):
+        t = env.from_string('''
+        {%- set wrapper = "<FOO>" %}
+        {%- for item in (1, 2, 3, 4) %}
+            {%- macro wrapper() %}[{{ item }}]{% endmacro %}
+            {{- wrapper() }}
+        {%- endfor %}
+        {{- wrapper -}}
+        ''')
+        assert t.render() == '[1][2][3][4]<FOO>'
+
+        t = env.from_string('''
+        {%- for item in (1, 2, 3, 4) %}
+            {%- macro wrapper() %}[{{ item }}]{% endmacro %}
+            {{- wrapper() }}
+        {%- endfor %}
+        {%- set wrapper = "<FOO>" %}
+        {{- wrapper -}}
+        ''')
+        assert t.render() == '[1][2][3][4]<FOO>'
+
+        t = env.from_string('''
+        {%- for item in (1, 2, 3, 4) %}
+            {%- macro wrapper() %}[{{ item }}]{% endmacro %}
+            {{- wrapper() }}
+        {%- endfor %}
+        {{- wrapper -}}
+        ''')
+        assert t.render(wrapper=23) == '[1][2][3][4]23'
+
+
+class BugTestCase(JinjaTestCase):
+
+    def test_keyword_folding(self):
+        env = Environment()
+        env.filters['testing'] = lambda value, some: value + some
+        assert env.from_string("{{ 'test'|testing(some='stuff') }}") \
+               .render() == 'teststuff'
+
+    def test_extends_output_bugs(self):
+        env = Environment(loader=DictLoader({
+            'parent.html': '(({% block title %}{% endblock %}))'
+        }))
+
+        t = env.from_string('{% if expr %}{% extends "parent.html" %}{% endif %}'
+                            '[[{% block title %}title{% endblock %}]]'
+                            '{% for item in [1, 2, 3] %}({{ item }}){% endfor %}')
+        assert t.render(expr=False) == '[[title]](1)(2)(3)'
+        assert t.render(expr=True) == '((title))'
+
+    def test_urlize_filter_escaping(self):
+        tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}')
+        assert tmpl.render() == '<a href="http://www.example.org/&lt;foo">http://www.example.org/&lt;foo</a>'
+
+    def test_loop_call_loop(self):
+        tmpl = env.from_string('''
+
+        {% macro test() %}
+            {{ caller() }}
+        {% endmacro %}
+
+        {% for num1 in range(5) %}
+            {% call test() %}
+                {% for num2 in range(10) %}
+                    {{ loop.index }}
+                {% endfor %}
+            {% endcall %}
+        {% endfor %}
+
+        ''')
+
+        assert tmpl.render().split() == map(unicode, range(1, 11)) * 5
+
+    def test_weird_inline_comment(self):
+        env = Environment(line_statement_prefix='%')
+        self.assert_raises(TemplateSyntaxError, env.from_string,
+                           '% for item in seq {# missing #}\n...% endfor')
+
+    def test_old_macro_loop_scoping_bug(self):
+        tmpl = env.from_string('{% for i in (1, 2) %}{{ i }}{% endfor %}'
+                               '{% macro i() %}3{% endmacro %}{{ i() }}')
+        assert tmpl.render() == '123'
+
+    def test_partial_conditional_assignments(self):
+        tmpl = env.from_string('{% if b %}{% set a = 42 %}{% endif %}{{ a }}')
+        assert tmpl.render(a=23) == '23'
+        assert tmpl.render(b=True) == '42'
+
+    def test_stacked_locals_scoping_bug(self):
+        env = Environment(line_statement_prefix='#')
+        t = env.from_string('''\
+# for j in [1, 2]:
+#   set x = 1
+#   for i in [1, 2]:
+#     print x
+#     if i % 2 == 0:
+#       set x = x + 1
+#     endif
+#   endfor
+# endfor
+# if a
+#   print 'A'
+# elif b
+#   print 'B'
+# elif c == d
+#   print 'C'
+# else
+#   print 'D'
+# endif
+    ''')
+        assert t.render(a=0, b=False, c=42, d=42.0) == '1111C'
+
+    def test_call_with_args(self):
+        t = Template("""{% macro dump_users(users) -%}
+        <ul>
+          {%- for user in users -%}
+            <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
+          {%- endfor -%}
+          </ul>
+        {%- endmacro -%}
+
+        {% call(user) dump_users(list_of_user) -%}
+          <dl>
+            <dl>Realname</dl>
+            <dd>{{ user.realname|e }}</dd>
+            <dl>Description</dl>
+            <dd>{{ user.description }}</dd>
+          </dl>
+        {% endcall %}""")
+
+        assert [x.strip() for x in t.render(list_of_user=[{
+            'username':'apo',
+            'realname':'something else',
+            'description':'test'
+        }]).splitlines()] == [
+            u'<ul><li><p>apo</p><dl>',
+            u'<dl>Realname</dl>',
+            u'<dd>something else</dd>',
+            u'<dl>Description</dl>',
+            u'<dd>test</dd>',
+            u'</dl>',
+            u'</li></ul>'
+        ]
+
+    def test_empty_if_condition_fails(self):
+        self.assert_raises(TemplateSyntaxError, Template, '{% if %}....{% endif %}')
+        self.assert_raises(TemplateSyntaxError, Template, '{% if foo %}...{% elif %}...{% endif %}')
+        self.assert_raises(TemplateSyntaxError, Template, '{% for x in %}..{% endfor %}')
+
+    def test_recursive_loop_bug(self):
+        tpl1 = Template("""
+        {% for p in foo recursive%}
+            {{p.bar}}
+            {% for f in p.fields recursive%}
+                {{f.baz}}
+                {{p.bar}}
+                {% if f.rec %}
+                    {{ loop(f.sub) }}
+                {% endif %}
+            {% endfor %}
+        {% endfor %}
+        """)
+
+        tpl2 = Template("""
+        {% for p in foo%}
+            {{p.bar}}
+            {% for f in p.fields recursive%}
+                {{f.baz}}
+                {{p.bar}}
+                {% if f.rec %}
+                    {{ loop(f.sub) }}
+                {% endif %}
+            {% endfor %}
+        {% endfor %}
+        """)
+
+    def test_correct_prefix_loader_name(self):
+        env = Environment(loader=PrefixLoader({
+            'foo':  DictLoader({})
+        }))
+        try:
+            env.get_template('foo/bar.html')
+        except TemplateNotFound, e:
+            assert e.name == 'foo/bar.html'
+        else:
+            assert False, 'expected error here'
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(CornerTestCase))
+    suite.addTest(unittest.makeSuite(BugTestCase))
+    return suite
index a17c3a3042f2c21ef06021c90dfd2f21e1e423d5..b2b4cf13e1fea49594389a71104f47d732c3d8ac 100644 (file)
@@ -59,6 +59,13 @@ class SandboxTestCase(JinjaTestCase):
         self.assert_raises(SecurityError, env.from_string(
             "{{ foo.__class__.__subclasses__() }}").render, foo=42)
 
+    def test_immutable_environment(self):
+        env = ImmutableSandboxedEnvironment()
+        self.assert_raises(SecurityError, env.from_string(
+            '{{ [].append(23) }}').render)
+        self.assert_raises(SecurityError, env.from_string(
+            '{{ {1:2}.clear() }}').render)
+
     def test_restricted(self):
         env = SandboxedEnvironment()
         self.assert_raises(TemplateSyntaxError, env.from_string,
diff --git a/jinja2/testsuite/utils.py b/jinja2/testsuite/utils.py
new file mode 100644 (file)
index 0000000..b18e8e6
--- /dev/null
@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+"""
+    jinja2.testsuite.utils
+    ~~~~~~~~~~~~~~~~~~~~~~
+
+    Tests utilities jinja uses.
+
+    :copyright: (c) 2010 by the Jinja Team.
+    :license: BSD, see LICENSE for more details.
+"""
+import os
+import unittest
+
+import pickle
+
+from jinja2 import Environment, Undefined, DebugUndefined, \
+     StrictUndefined, UndefinedError, Template, meta
+from jinja2.utils import LRUCache
+
+
+class LRUCacheTestCase(JinjaTestCase):
+
+    def test_simple(self):
+        d = LRUCache(3)
+        d["a"] = 1
+        d["b"] = 2
+        d["c"] = 3
+        d["a"]
+        d["d"] = 4
+        assert len(d) == 3
+        assert 'a' in d and 'c' in d and 'd' in d and 'b' not in d
+
+    def test_pickleable(self):
+        cache = LRUCache(2)
+        cache["foo"] = 42
+        cache["bar"] = 23
+        cache["foo"]
+
+        for protocol in range(3):
+            copy = pickle.loads(pickle.dumps(cache, protocol))
+            assert copy.capacity == cache.capacity
+            assert copy._mapping == cache._mapping
+            assert copy._queue == cache._queue
+
+
+class MarkupLeakTestCase(JinjaTestCase):
+
+    def test_markup_leaks(self):
+        counts = set()
+        for count in xrange(20):
+            for item in xrange(1000):
+                escape("foo")
+                escape("<foo>")
+                escape(u"foo")
+                escape(u"<foo>")
+            counts.add(len(gc.get_objects()))
+        assert len(counts) == 1, 'ouch, c extension seems to leak objects'
+
+
+def suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(LRUCacheTestCase))
+
+    # this test only tests the c extension
+    if not hasattr(escape, 'func_code'):
+        suite.addTest(unittest.makeSuite(MarkupLeakTestCase))
+
+    return suite