From: Armin Ronacher Date: Tue, 9 Feb 2010 15:05:08 +0000 (+0100) Subject: Started working on unittest powered testsuite. X-Git-Tag: 2.3~22 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=7af781c446de40c06cb587eb3b6b6c78c06cb52f;p=jinja2.git Started working on unittest powered testsuite. --HG-- branch : trunk --- diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 59d06c7..672d696 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -6,7 +6,7 @@ Compiles nodes into python code. :copyright: (c) 2010 by the Jinja Team. - :license: BSD. + :license: BSD, see LICENSE for more details. """ from cStringIO import StringIO from itertools import chain diff --git a/jinja2/testsuite/__init__.py b/jinja2/testsuite/__init__.py new file mode 100644 index 0000000..c0327c1 --- /dev/null +++ b/jinja2/testsuite/__init__.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" + jinja2.testsuite + ~~~~~~~~~~~~~~~~ + + All the unittests of Jinja2. These tests can be executed by + either running run-tests.py using multiple Python versions at + the same time. + + :copyright: (c) 2010 by the Jinja Team. + :license: BSD, see LICENSE for more details. +""" +import os +import sys +import unittest +from jinja2 import loaders + + +here = os.path.dirname(os.path.abspath(__file__)) + +dict_loader = loaders.DictLoader({ + 'justdict.html': 'FOO' +}) +package_loader = loaders.PackageLoader('jinja2.testsuite.res', 'templates') +filesystem_loader = loaders.FileSystemLoader(here + 'res/templates') +function_loader = loaders.FunctionLoader({'justfunction.html': 'FOO'}.get) +choice_loader = loaders.ChoiceLoader([dict_loader, package_loader]) +prefix_loader = loaders.PrefixLoader({ + 'a': filesystem_loader, + 'b': dict_loader +}) + + +class JinjaTestCase(unittest.TestCase): + + ### use only these methods for testing. If you need standard + ### unittest method, wrap them! + + def assert_equal(self, a, b): + return self.assertEqual(a, b) + + def assert_raises(self, *args, **kwargs): + return self.assertRaises(*args, **kwargs) + + +def suite(): + from jinja2.testsuite import ext, filters + suite = unittest.TestSuite() + suite.addTest(ext.suite()) + suite.addTest(filters.suite()) + return suite diff --git a/jinja2/testsuite/ext.py b/jinja2/testsuite/ext.py new file mode 100644 index 0000000..57298ac --- /dev/null +++ b/jinja2/testsuite/ext.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +""" + jinja2.testsuite.ext + ~~~~~~~~~~~~~~~~~~~~ + + Tests for the extensions. + + :copyright: (c) 2010 by the Jinja Team. + :license: BSD, see LICENSE for more details. +""" +import re +import unittest + +from jinja2.testsuite import JinjaTestCase, filesystem_loader + +from jinja2 import Environment, nodes +from jinja2.ext import Extension +from jinja2.lexer import Token, count_newlines + + +importable_object = 23 + +_gettext_re = re.compile(r'_\((.*?)\)(?s)') + + +class TestExtension(Extension): + tags = set(['test']) + ext_attr = 42 + + def parse(self, parser): + return nodes.Output([self.call_method('_dump', [ + nodes.EnvironmentAttribute('sandboxed'), + self.attr('ext_attr'), + nodes.ImportedName(__name__ + '.importable_object'), + nodes.ContextReference() + ])]).set_lineno(parser.stream.next().lineno) + + def _dump(self, sandboxed, ext_attr, imported_object, context): + return '%s|%s|%s|%s' % ( + sandboxed, + ext_attr, + imported_object, + context.blocks + ) + + +class PreprocessorExtension(Extension): + + def preprocess(self, source, name, filename=None): + return source.replace('[[TEST]]', '({{ foo }})') + + +class StreamFilterExtension(Extension): + + def filter_stream(self, stream): + for token in stream: + if token.type == 'data': + for t in self.interpolate(token): + yield t + else: + yield token + + def interpolate(self, token): + pos = 0 + end = len(token.value) + lineno = token.lineno + while 1: + match = _gettext_re.search(token.value, pos) + if match is None: + break + value = token.value[pos:match.start()] + if value: + yield Token(lineno, 'data', value) + lineno += count_newlines(token.value) + yield Token(lineno, 'variable_begin', None) + yield Token(lineno, 'name', 'gettext') + yield Token(lineno, 'lparen', None) + yield Token(lineno, 'string', match.group(1)) + yield Token(lineno, 'rparen', None) + yield Token(lineno, 'variable_end', None) + pos = match.end() + if pos < end: + yield Token(lineno, 'data', token.value[pos:]) + + +class ExtensionsTestCase(JinjaTestCase): + + def test_loop_controls(self): + env = Environment(extensions=['jinja2.ext.loopcontrols']) + + tmpl = env.from_string(''' + {%- for item in [1, 2, 3, 4] %} + {%- if item % 2 == 0 %}{% continue %}{% endif -%} + {{ item }} + {%- endfor %}''') + assert tmpl.render() == '13' + + tmpl = env.from_string(''' + {%- for item in [1, 2, 3, 4] %} + {%- if item > 2 %}{% break %}{% endif -%} + {{ item }} + {%- endfor %}''') + assert tmpl.render() == '12' + + def test_do(self): + env = Environment(extensions=['jinja2.ext.do']) + tmpl = env.from_string(''' + {%- set items = [] %} + {%- for char in "foo" %} + {%- do items.append(loop.index0 ~ char) %} + {%- endfor %}{{ items|join(', ') }}''') + assert tmpl.render() == '0f, 1o, 2o' + + def test_with(self): + env = Environment(extensions=['jinja2.ext.with_']) + tmpl = env.from_string('''\ + {% with a=42, b=23 -%} + {{ a }} = {{ b }} + {% endwith -%} + {{ a }} = {{ b }}\ + ''') + assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \ + == ['42 = 23', '1 = 2'] + + def test_extension_nodes(self): + env = Environment(extensions=[TestExtension]) + tmpl = env.from_string('{% test %}') + assert tmpl.render() == 'False|42|23|{}' + + def test_identifier(self): + assert TestExtension.identifier == __name__ + '.TestExtension' + + def test_rebinding(self): + original = Environment(extensions=[TestExtension]) + overlay = original.overlay() + for env in original, overlay: + for ext in env.extensions.itervalues(): + assert ext.environment is env + + def test_preprocessor_extension(self): + env = Environment(extensions=[PreprocessorExtension]) + tmpl = env.from_string('{[[TEST]]}') + assert tmpl.render(foo=42) == '{(42)}' + + def test_streamfilter_extension(self): + env = Environment(extensions=[StreamFilterExtension]) + env.globals['gettext'] = lambda x: x.upper() + tmpl = env.from_string('Foo _(bar) Baz') + out = tmpl.render() + assert out == 'Foo BAR Baz' + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(ExtensionsTestCase)) + return suite diff --git a/jinja2/testsuite/filters.py b/jinja2/testsuite/filters.py new file mode 100644 index 0000000..e3bca02 --- /dev/null +++ b/jinja2/testsuite/filters.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- +""" + jinja2.testsuite.filters + ~~~~~~~~~~~~~~~~~~~~~~~~ + + Tests for the jinja filters. + + :copyright: (c) 2010 by the Jinja Team. + :license: BSD, see LICENSE for more details. +""" +import unittest +from jinja2.testsuite import JinjaTestCase + +from jinja2 import Markup, Environment + +env = Environment() + + +CAPITALIZE = '''{{ "foo bar"|capitalize }}''' +CENTER = '''{{ "foo"|center(9) }}''' +DEFAULT = '''{{ missing|default("no") }}|{{ false|default('no') }}|\ +{{ false|default('no', true) }}|{{ given|default("no") }}''' +DICTSORT = '''{{ foo|dictsort }}|\ +{{ foo|dictsort(true) }}|\ +{{ foo|dictsort(false, 'value') }}''' +BATCH = '''{{ foo|batch(3)|list }}|{{ foo|batch(3, 'X')|list }}''' +SLICE = '''{{ foo|slice(3)|list }}|{{ foo|slice(3, 'X')|list }}''' +ESCAPE = '''{{ '<">&'|escape }}''' +STRIPTAGS = '''{{ foo|striptags }}''' +FILESIZEFORMAT = '{{ 100|filesizeformat }}|\ +{{ 1000|filesizeformat }}|\ +{{ 1000000|filesizeformat }}|\ +{{ 1000000000|filesizeformat }}|\ +{{ 1000000000000|filesizeformat }}|\ +{{ 100|filesizeformat(true) }}|\ +{{ 1000|filesizeformat(true) }}|\ +{{ 1000000|filesizeformat(true) }}|\ +{{ 1000000000|filesizeformat(true) }}|\ +{{ 1000000000000|filesizeformat(true) }}' +FIRST = '''{{ foo|first }}''' +FLOAT = '''{{ "42"|float }}|{{ "ajsghasjgd"|float }}|{{ "32.32"|float }}''' +FORMAT = '''{{ "%s|%s"|format("a", "b") }}''' +INDENT = '''{{ foo|indent(2) }}|{{ foo|indent(2, true) }}''' +INT = '''{{ "42"|int }}|{{ "ajsghasjgd"|int }}|{{ "32.32"|int }}''' +JOIN = '''{{ [1, 2, 3]|join("|") }}''' +LAST = '''{{ foo|last }}''' +LENGTH = '''{{ "hello world"|length }}''' +LOWER = '''{{ "FOO"|lower }}''' +PPRINT = '''{{ data|pprint }}''' +RANDOM = '''{{ seq|random }}''' +REVERSE = '''{{ "foobar"|reverse|join }}|{{ [1, 2, 3]|reverse|list }}''' +STRING = '''{{ range(10)|string }}''' +TITLE = '''{{ "foo bar"|title }}''' +TRIM = '''{{ " foo "|trim }}''' +TRUNCATE = '''{{ data|truncate(15, true, ">>>") }}|\ +{{ data|truncate(15, false, ">>>") }}|\ +{{ smalldata|truncate(15) }}''' +UPPER = '''{{ "foo"|upper }}''' +URLIZE = '''{{ "foo http://www.example.com/ bar"|urlize }}''' +WORDCOUNT = '''{{ "foo bar baz"|wordcount }}''' +BLOCK = '''{% filter lower|escape %}{% endfilter %}''' +CHAINING = '''{{ ['', '']|first|upper|escape }}''' +SUM = '''{{ [1, 2, 3, 4, 5, 6]|sum }}''' +ABS = '''{{ -1|abs }}|{{ 1|abs }}''' +ROUND = '''{{ 2.7|round }}|{{ 2.1|round }}|\ +{{ 2.1234|round(2, 'floor') }}|{{ 2.1|round(0, 'ceil') }}''' +XMLATTR = '''{{ {'foo': 42, 'bar': 23, 'fish': none, +'spam': missing, 'blub:blub': ''}|xmlattr }}''' +SORT1 = '''{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}''' +GROUPBY = '''{% for grouper, list in [{'foo': 1, 'bar': 2}, + {'foo': 2, 'bar': 3}, + {'foo': 1, 'bar': 1}, + {'foo': 3, 'bar': 4}]|groupby('foo') -%} +{{ grouper }}: {{ list|join(', ') }} +{% endfor %}''' +FILTERTAG = '''{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}''' +SORT2 = '''{{ ['foo', 'Bar', 'blah']|sort }}''' + + +class FilterTestCase(JinjaTestCase): + + def test_capitalize(self): + tmpl = env.from_string('{{ "foo bar"|capitalize }}') + assert tmpl.render() == 'Foo bar' + + def test_center(self): + tmpl = env.from_string('{{ "foo"|center(9) }}') + assert tmpl.render() == ' foo ' + + def test_default(self): + tmpl = env.from_string( + "{{ missing|default('no') }}|{{ false|default('no') }}|" + "{{ false|default('no', true) }}|{{ given|default('no') }}" + ) + assert tmpl.render(given='yes') == 'no|False|no|yes' + + def test_dictsort(self): + tmpl = env.from_string( + '{{ foo|dictsort }}|' + '{{ foo|dictsort(true) }}|' + '{{ foo|dictsort(false, "value") }}' + ) + out = tmpl.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3}) + assert out == ("[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]|" + "[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]|" + "[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]") + + def test_batch(self): + tmpl = env.from_string("{{ foo|batch(3)|list }}|" + "{{ foo|batch(3, 'X')|list }}") + out = tmpl.render(foo=range(10)) + assert out == ("[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|" + "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]") + + def test_slice(self): + tmpl = env.from_string('{{ foo|slice(3)|list }}|' + '{{ foo|slice(3, "X")|list }}') + out = tmpl.render(foo=range(10)) + assert out == ("[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|" + "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]") + + def test_escape(self): + tmpl = env.from_string('''{{ '<">&'|escape }}''') + out = tmpl.render() + assert out == '<">&' + + def test_striptags(self): + tmpl = env.from_string('''{{ foo|striptags }}''') + out = tmpl.render(foo='

just a small \n ' + 'example link

\n

to a webpage

' + '') + assert out == 'just a small example link to a webpage' + + def test_filesizeformat(self): + tmpl = env.from_string( + '{{ 100|filesizeformat }}|' + '{{ 1000|filesizeformat }}|' + '{{ 1000000|filesizeformat }}|' + '{{ 1000000000|filesizeformat }}|' + '{{ 1000000000000|filesizeformat }}|' + '{{ 100|filesizeformat(true) }}|' + '{{ 1000|filesizeformat(true) }}|' + '{{ 1000000|filesizeformat(true) }}|' + '{{ 1000000000|filesizeformat(true) }}|' + '{{ 1000000000000|filesizeformat(true) }}' + ) + out = tmpl.render() + assert out == ( + '100 Bytes|1.0 KB|1.0 MB|1.0 GB|1000.0 GB|' + '100 Bytes|1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB' + ) + + def test_first(self): + tmpl = env.from_string('{{ foo|first }}') + out = tmpl.render(foo=range(10)) + assert out == '0' + + def test_float(self): + tmpl = env.from_string('{{ "42"|float }}|' + '{{ "ajsghasjgd"|float }}|' + '{{ "32.32"|float }}') + out = tmpl.render() + assert out == '42.0|0.0|32.32' + + def test_format(self): + tmpl = env.from_string('''{{ "%s|%s"|format("a", "b") }}''') + out = tmpl.render() + assert out == 'a|b' + + def test_indent(self): + tmpl = env.from_string('{{ foo|indent(2) }}|{{ foo|indent(2, true) }}') + text = '\n'.join([' '.join(['foo', 'bar'] * 2)] * 2) + out = tmpl.render(foo=text) + assert out == ('foo bar foo bar\n foo bar foo bar| ' + 'foo bar foo bar\n foo bar foo bar') + + def test_int(self): + tmpl = env.from_string('{{ "42"|int }}|{{ "ajsghasjgd"|int }}|' + '{{ "32.32"|int }}') + out = tmpl.render() + assert out == '42|0|32' + + def test_join(self): + tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}') + out = tmpl.render() + assert out == '1|2|3' + + env2 = Environment(autoescape=True) + tmpl = env2.from_string('{{ ["", "foo"|safe]|join }}') + assert tmpl.render() == '<foo>foo' + + def test_last(self): + tmpl = env.from_string('''{{ foo|last }}''') + out = tmpl.render(foo=range(10)) + assert out == '9' + + def test_length(self): + tmpl = env.from_string('''{{ "hello world"|length }}''') + out = tmpl.render() + assert out == '11' + + def test_lower(self): + tmpl = env.from_string('''{{ "FOO"|lower }}''') + out = tmpl.render() + assert out == 'foo' + + def test_pprint(self): + from pprint import pformat + tmpl = env.from_string('''{{ data|pprint }}''') + data = range(1000) + assert tmpl.render(data=data) == pformat(data) + + def test_random(self): + tmpl = env.from_string('''{{ seq|random }}''') + seq = range(100) + for _ in range(10): + assert int(tmpl.render(seq=seq)) in seq + + def test_reverse(self): + tmpl = env.from_string('{{ "foobar"|reverse|join }}|' + '{{ [1, 2, 3]|reverse|list }}') + assert tmpl.render() == 'raboof|[3, 2, 1]' + + def test_string(self): + tmpl = env.from_string('''{{ range(10)|string }}''') + assert tmpl.render(foo=range(10)) == unicode(xrange(10)) + + def test_title(self): + tmpl = env.from_string('''{{ "foo bar"|title }}''') + assert tmpl.render() == "Foo Bar" + + def test_truncate(self): + tmpl = env.from_string( + '{{ data|truncate(15, true, ">>>") }}|' + '{{ data|truncate(15, false, ">>>") }}|' + '{{ smalldata|truncate(15) }}' + ) + out = tmpl.render(data='foobar baz bar' * 1000, + smalldata='foobar baz bar') + assert out == 'foobar baz barf>>>|foobar baz >>>|foobar baz bar' + + def test_upper(self): + tmpl = env.from_string('{{ "foo"|upper }}') + assert tmpl.render() == 'FOO' + + def test_urlize(self): + tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}') + assert tmpl.render() == 'foo '\ + 'http://www.example.com/ bar' + + def test_wordcount(self): + tmpl = env.from_string('{{ "foo bar baz"|wordcount }}') + assert tmpl.render() == '3' + + def test_block(self): + tmpl = env.from_string('{% filter lower|escape %}{% endfilter %}') + assert tmpl.render() == '<hehe>' + + def test_chaining(self): + tmpl = env.from_string('''{{ ['', '']|first|upper|escape }}''') + assert tmpl.render() == '<FOO>' + + def test_sum(self): + tmpl = env.from_string('''{{ [1, 2, 3, 4, 5, 6]|sum }}''') + assert tmpl.render() == '21' + + def test_abs(self): + tmpl = env.from_string('''{{ -1|abs }}|{{ 1|abs }}''') + return tmpl.render() == '1|1' + + def test_round(self): + tmpl = env.from_string('{{ 2.7|round }}|{{ 2.1|round }}|' + "{{ 2.1234|round(2, 'floor') }}|" + "{{ 2.1|round(0, 'ceil') }}") + return tmpl.render() == '3.0|2.0|2.1|3.0' + + def test_xmlattr(self): + tmpl = env.from_string("{{ {'foo': 42, 'bar': 23, 'fish': none, " + "'spam': missing, 'blub:blub': ''}|xmlattr }}") + out = tmpl.render().split() + assert len(out) == 3 + assert 'foo="42"' in out + assert 'bar="23"' in out + assert 'blub:blub="<?>"' in out + + def test_sort1(self): + tmpl = env.from_string('{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}') + assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]' + + def test_groupby(self): + tmpl = env.from_string(''' + {%- for grouper, list in [{'foo': 1, 'bar': 2}, + {'foo': 2, 'bar': 3}, + {'foo': 1, 'bar': 1}, + {'foo': 3, 'bar': 4}]|groupby('foo') -%} + {{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}| + {%- endfor %}''') + assert tmpl.render().split('|') == [ + "1: 1, 2: 1, 1", + "2: 2, 3", + "3: 3, 4", + "" + ] + + def test_filtertag(self): + tmpl = env.from_string("{% filter upper|replace('FOO', 'foo') %}" + "foobar{% endfilter %}") + assert tmpl.render() == 'fooBAR' + + def test_replace(self): + env = Environment() + tmpl = env.from_string('{{ string|replace("o", 42) }}') + assert tmpl.render(string='') == '' + env = Environment(autoescape=True) + tmpl = env.from_string('{{ string|replace("o", 42) }}') + assert tmpl.render(string='') == '<f4242>' + tmpl = env.from_string('{{ string|replace("<", 42) }}') + assert tmpl.render(string='') == '42foo>' + tmpl = env.from_string('{{ string|replace("o", ">x<") }}') + assert tmpl.render(string=Markup('foo')) == 'f>x<>x<' + + def test_forceescape(self): + tmpl = env.from_string('{{ x|forceescape }}') + assert tmpl.render(x=Markup('
')) == u'<div />' + + def test_safe(self): + env = Environment(autoescape=True) + tmpl = env.from_string('{{ "
foo
"|safe }}') + assert tmpl.render() == '
foo
' + tmpl = env.from_string('{{ "
foo
" }}') + assert tmpl.render() == '<div>foo</div>' + + def test_sort2(self): + tmpl = env.from_string('''{{ ['foo', 'Bar', 'blah']|sort }}''') + assert tmpl.render() == "['Bar', 'blah', 'foo']" + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(FilterTestCase)) + return suite diff --git a/jinja2/testsuite/res/__init__.py b/jinja2/testsuite/res/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/jinja2/testsuite/res/templates/broken.html b/jinja2/testsuite/res/templates/broken.html new file mode 100644 index 0000000..77669fa --- /dev/null +++ b/jinja2/testsuite/res/templates/broken.html @@ -0,0 +1,3 @@ +Before +{{ fail() }} +After diff --git a/jinja2/testsuite/res/templates/foo/test.html b/jinja2/testsuite/res/templates/foo/test.html new file mode 100644 index 0000000..b7d6715 --- /dev/null +++ b/jinja2/testsuite/res/templates/foo/test.html @@ -0,0 +1 @@ +FOO diff --git a/jinja2/testsuite/res/templates/syntaxerror.html b/jinja2/testsuite/res/templates/syntaxerror.html new file mode 100644 index 0000000..f21b817 --- /dev/null +++ b/jinja2/testsuite/res/templates/syntaxerror.html @@ -0,0 +1,4 @@ +Foo +{% for item in broken %} + ... +{% endif %} diff --git a/jinja2/testsuite/res/templates/test.html b/jinja2/testsuite/res/templates/test.html new file mode 100644 index 0000000..ba578e4 --- /dev/null +++ b/jinja2/testsuite/res/templates/test.html @@ -0,0 +1 @@ +BAR diff --git a/setup.py b/setup.py index 134cc97..f69fecc 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ setup( 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Text Processing :: Markup :: HTML' ], - packages=['jinja2'], + packages=['jinja2', 'jinja2.testsuite'], features={ 'speedups': Feature("optional C speed-enhancements", standard=False, @@ -86,6 +86,7 @@ setup( ) }, extras_require={'i18n': ['Babel>=0.8']}, + test_suite='jinja2.testsuite.suite', entry_points=""" [babel.extractors] jinja2 = jinja2.ext:babel_extract[i18n]