From: Armin Ronacher Date: Mon, 21 May 2007 14:44:26 +0000 (+0200) Subject: [svn] added many new tests to jinja X-Git-Tag: 2.0rc1~325 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ccf284bd2fdd400588aa9d9df3ca929917f4a162;p=thirdparty%2Fjinja.git [svn] added many new tests to jinja --HG-- branch : trunk --- diff --git a/CHANGES b/CHANGES index 0c0fd47c..bda176c0 100644 --- a/CHANGES +++ b/CHANGES @@ -81,7 +81,8 @@ Version 1.1 - additional macro arguments now end up in `varargs`. -- implemented `{% call %}` - unsure if this makes it into the final release. +- implemented the `{% call %}` block. `call` and `endcall` can still be used + as identifiers until Jinja 1.3 - it's not possible to stream templates. diff --git a/Makefile b/Makefile index ca294d90..8f40d473 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,9 @@ test: @(cd tests; py.test $(TESTS)) +test-coverage: + @(cd tests; py.test -C $(TESTS)) + documentation: @(cd docs; ./generate.py) diff --git a/docs/generate.py b/docs/generate.py index 8acf5b76..f8e8238d 100755 --- a/docs/generate.py +++ b/docs/generate.py @@ -260,7 +260,7 @@ def generate_documentation(data, link_style): def handle_file(filename, fp, dst, preproc): now = datetime.now() title = os.path.basename(filename)[:-4] - content = fp.read() + content = fp.read().decode('utf-8') suffix = not preproc and '.html' or '' parts = generate_documentation(content, (lambda x: './%s%s' % (x, suffix))) result = file(os.path.join(dst, title + '.html'), 'w') diff --git a/docs/src/altsyntax.txt b/docs/src/altsyntax.txt index 088445c1..9fb3b64f 100644 --- a/docs/src/altsyntax.txt +++ b/docs/src/altsyntax.txt @@ -46,7 +46,7 @@ An example template then looks like this: @@ -82,7 +82,7 @@ Jinja 1.1 or higher. Block / Variable Tag Unification --------------------------------- +================================ If variable end and start tags are `None` or look the same as block tags and you're running Jinja 1.1 or later the parser will switch into the @@ -103,3 +103,6 @@ This now allows smarty like templates: {else} Something is {something}. {endif} + +This feature however can cause strange looking templates because there is no +visible difference between blocks and variables. diff --git a/docs/src/designerdoc.txt b/docs/src/designerdoc.txt index ba5de713..d48dde20 100644 --- a/docs/src/designerdoc.txt +++ b/docs/src/designerdoc.txt @@ -419,7 +419,11 @@ The following keywords exist and cannot be used as identifiers: `and`, `block`, `cycle`, `elif`, `else`, `endblock`, `endfilter`, `endfor`, `endif`, `endmacro`, `endraw`, `endtrans`, `extends`, `filter`, `for`, `if`, `in`, `include`, `is`, `macro`, `not`, `or`, `pluralize`, - `print`, `raw`, `recursive`, `set`, `trans` + `print`, `raw`, `recursive`, `set`, `trans`, `call`*, `endcall`*, + +keywords marked with `*` can be used until Jinja 1.3 as identifiers because +they were introduced after the Jinja 1.0 release and can cause backwards +compatiblity problems otherwise. You should not use them any more. If you want to use such a name you have to prefix or suffix it or use alternative names: @@ -430,11 +434,6 @@ alternative names: {{ macro_('foo') }} {% endfor %} -If future Jinja releases add new keywords those will be "light" keywords which -means that they won't raise an error for several releases but yield warnings -on the application side. But it's very unlikely that new keywords will be -added. - Bleeding Edge ============= diff --git a/docs/src/devrecipies.txt b/docs/src/devrecipies.txt index ae70226d..7f5b6c01 100644 --- a/docs/src/devrecipies.txt +++ b/docs/src/devrecipies.txt @@ -23,16 +23,17 @@ this: class AutoEnvironment(Environment): - def autorender(self): - return self.render(sys._getframe(1).f_locals) + def autorender(self, template): + tmpl = self.get_template(template) + return tmpl.render(sys._getframe(1).f_locals) You can use it now like this: .. sourcecode:: python - foo_tmpl = env.get_template('foo.html') - def foo(): seq = range(10) foo = "blub" - return foo_tmpl.autorender() + return env.autorender('foo.html') + +In the template you can now access the local variables `seq` and `foo`. diff --git a/docs/src/objects.txt b/docs/src/objects.txt index b1f86e4c..7b0641fa 100644 --- a/docs/src/objects.txt +++ b/docs/src/objects.txt @@ -49,6 +49,30 @@ from the template it should return the content of the context as simple html template. Of course you can modify the context too. For more informations about the context object have a look at the `context object`_ documentation. +The new ``{% call %}`` tag that exists with Jinja 1.1 onwards can not only +be used with Jinja macros but also with Python functions. If a template +designers uses the ``{% call %}`` tag to call a function provided by the +application Jinja will call this function with a keyword argument called +`caller` which points to a `function`. If you call this function (optionally +with keyword arguments that appear in the context) you get a string back +that was the content of that block. This should explain this: + +.. sourcecode:: python + + def make_dialog(title, caller=None): + body = '' + if caller: + body = caller(title=title) + return '

%s

%s
' % (title, body) + +This can be used like this in the template now: + +.. sourcecode:: html+jinja + + {% call make_dialog('Dialog Title') %} + This is the body of the dialog entitled "{{ title }}". + {% endcall %} + Deferred Values =============== diff --git a/jinja/_native.py b/jinja/_native.py index ab7d1d39..9fa723f5 100644 --- a/jinja/_native.py +++ b/jinja/_native.py @@ -14,6 +14,20 @@ :license: BSD, see LICENSE for more details. """ from jinja.datastructure import Deferred, Undefined +try: + from collections import deque +except ImportError: + class deque(list): + """ + Minimal subclass of list that provides the deque + interface used by the native `BaseContext`. + """ + + def appendleft(self, item): + list.insert(self, 0, item) + + def popleft(self): + return list.pop(self, 0) class BaseContext(object): @@ -21,27 +35,33 @@ class BaseContext(object): def __init__(self, undefined_singleton, globals, initial): self._undefined_singleton = undefined_singleton self.current = current = {} - self.stack = [globals, initial, current] - self._push = self.stack.append - self._pop = self.stack.pop + self._stack = deque([current, initial, globals]) self.globals = globals self.initial = initial + self._push = self._stack.appendleft + self._pop = self._stack.popleft + + def stack(self): + return list(self._stack)[::-1] + stack = property(stack) + def pop(self): """ Pop the last layer from the stack and return it. """ rv = self._pop() - self.current = self.stack[-1] + self.current = self._stack[0] return rv def push(self, data=None): """ - Push one layer to the stack. Layer must be a dict or omitted. + Push one layer to the stack and return it. Layer must be + a dict or omitted. """ data = data or {} self._push(data) - self.current = self.stack[-1] + self.current = self._stack[0] return data def __getitem__(self, name): @@ -50,10 +70,7 @@ class BaseContext(object): such as ``'::cycle1'``. Resolve deferreds. """ if not name.startswith('::'): - # because the stack is usually quite small we better - # use [::-1] which is faster than reversed() in such - # a situation. - for d in self.stack[::-1]: + for d in self._stack: if name in d: rv = d[name] if rv.__class__ is Deferred: @@ -83,7 +100,7 @@ class BaseContext(object): """ Check if the context contains a given variable. """ - for layer in self.stack: + for layer in self._stack: if name in layer: return True return False diff --git a/jinja/datastructure.py b/jinja/datastructure.py index 3b8e4485..daa2e27a 100644 --- a/jinja/datastructure.py +++ b/jinja/datastructure.py @@ -547,7 +547,7 @@ class TemplateStream(object): def __init__(self, gen): self._gen = gen - self._next = gen._next + self._next = gen.next self.buffered = False def disable_buffering(self): diff --git a/jinja/lexer.py b/jinja/lexer.py index e948aa08..7b98a57c 100644 --- a/jinja/lexer.py +++ b/jinja/lexer.py @@ -64,7 +64,7 @@ keywords = set(['and', 'block', 'cycle', 'elif', 'else', 'endblock', 'endfilter', 'endfor', 'endif', 'endmacro', 'endraw', 'endtrans', 'extends', 'filter', 'for', 'if', 'in', 'include', 'is', 'macro', 'not', 'or', 'pluralize', 'raw', - 'recursive', 'set', 'trans', 'print', 'call', 'endcall']) + 'recursive', 'set', 'trans', 'print']) class Failure(object): diff --git a/jinja/loaders.py b/jinja/loaders.py index 0bebdc3e..15d04e68 100644 --- a/jinja/loaders.py +++ b/jinja/loaders.py @@ -62,6 +62,12 @@ class LoaderWrapper(object): else: self.available = True + def __getattr__(self, name): + """ + Not found attributes are redirected to the loader + """ + return getattr(self.loader, name) + def get_source(self, name, parent=None): """Retrieve the sourcecode of a template.""" # just ascii chars are allowed as template names @@ -336,6 +342,12 @@ class PackageLoader(CachedLoaderMixin, BaseLoader): to ``package_name + '/' + package_path``. *New in Jinja 1.1* =================== ================================================= + + Important note: If you're using an application that is inside of an + egg never set `auto_reload` to `True`. The egg resource manager will + automatically export files to the file system and touch them so that + you not only end up with additional temporary files but also an automatic + reload each time you load a template. """ def __init__(self, package_name, package_path, use_memcache=False, @@ -347,11 +359,6 @@ class PackageLoader(CachedLoaderMixin, BaseLoader): self.package_path = package_path if cache_salt is None: cache_salt = package_name + '/' + package_path - # if we have an loader we probably retrieved it from an egg - # file. In that case don't use the auto_reload! - if auto_reload and getattr(__import__(package_name, '', '', ['']), - '__loader__', None) is not None: - auto_reload = False CachedLoaderMixin.__init__(self, use_memcache, memcache_size, cache_folder, auto_reload, cache_salt) diff --git a/jinja/parser.py b/jinja/parser.py index b3e1ba13..befcbc53 100644 --- a/jinja/parser.py +++ b/jinja/parser.py @@ -41,7 +41,7 @@ switch_if = StateTest.expect_name('else', 'elif', 'endif') end_of_if = StateTest.expect_name('endif') end_of_filter = StateTest.expect_name('endfilter') end_of_macro = StateTest.expect_name('endmacro') -end_of_call = StateTest.expect_name('endcall') +end_of_call = StateTest.expect_name('endcall_') end_of_block_tag = StateTest.expect_name('endblock') end_of_trans = StateTest.expect_name('endtrans') @@ -54,6 +54,8 @@ class Parser(object): """ def __init__(self, environment, source, filename=None): + #XXX: with Jinja 1.3 call becomes a keyword. Add it also + # to the lexer.py file. self.environment = environment if isinstance(source, str): source = source.decode(environment.template_charset, 'ignore') @@ -78,7 +80,7 @@ class Parser(object): 'filter': self.handle_filter_directive, 'print': self.handle_print_directive, 'macro': self.handle_macro_directive, - 'call': self.handle_call_directive, + 'call_': self.handle_call_directive, 'block': self.handle_block_directive, 'extends': self.handle_extends_directive, 'include': self.handle_include_directive, diff --git a/jinja/translators/python.py b/jinja/translators/python.py index 7b85d780..12a829ad 100644 --- a/jinja/translators/python.py +++ b/jinja/translators/python.py @@ -423,11 +423,7 @@ class PythonTranslator(Translator): # handle requirements code if requirements: - requirement_lines = [ - 'def bootstrap(context):', - ' ctx_push = context.push', - ' ctx_pop = context.pop' - ] + requirement_lines = ['def bootstrap(context):'] has_requirements = False for n in requirements: requirement_lines.append(self.handle_node(n)) @@ -452,8 +448,6 @@ class PythonTranslator(Translator): if data: block_lines.extend([ 'def %s(context):' % func_name, - ' ctx_push = context.push', - ' ctx_pop = context.pop', self.indent(self.nodeinfo(item, True)), data, ' if 0: yield None\n' @@ -491,9 +485,7 @@ class PythonTranslator(Translator): '# Name for disabled debugging\n' '__name__ = %r\n\n' 'def generate(context):\n' - ' assert environment is context.environment\n' - ' ctx_push = context.push\n' - ' ctx_pop = context.pop' % ( + ' assert environment is context.environment' % ( '\n'.join([ '%s = environment.%s' % (item, item) for item in ['get_attribute', 'perform_test', 'apply_filters', @@ -570,14 +562,8 @@ class PythonTranslator(Translator): """ Handle data around nodes. """ - # if we have a ascii only string we go with the - # bytestring. otherwise we go with the unicode object - try: - data = str(node.text) - except UnicodeError: - data = node.text return self.indent(self.nodeinfo(node)) + '\n' +\ - self.indent('yield %r' % data) + self.indent('yield %r' % node.text) def handle_dynamic_text(self, node): """ @@ -620,7 +606,7 @@ class PythonTranslator(Translator): buf = [] write = lambda x: buf.append(self.indent(x)) write(self.nodeinfo(node)) - write('ctx_push()') + write('context.push()') # recursive loops if node.recursive: @@ -668,7 +654,7 @@ class PythonTranslator(Translator): write('yield item') self.indention -= 1 - write('ctx_pop()') + write('context.pop()') return '\n'.join(buf) def handle_if_condition(self, node): @@ -801,7 +787,7 @@ class PythonTranslator(Translator): if varargs_init: arg_items.append(varargs_init) - write('ctx_push({%s})' % ',\n '.join([ + write('context.push({%s})' % ',\n '.join([ idx and self.indent(item) or item for idx, item in enumerate(arg_items) ])) @@ -818,7 +804,7 @@ class PythonTranslator(Translator): data = self.handle_node(node.body) if data: buf.append(data) - write('ctx_pop()') + write('context.pop()') write('if 0: yield None') self.indention -= 1 buf.append(self.indent('context[%r] = buffereater(macro)' % @@ -836,11 +822,11 @@ class PythonTranslator(Translator): write('def call(**kwargs):') self.indention += 1 - write('ctx_push(kwargs)') + write('context.push(kwargs)') data = self.handle_node(node.body) if data: buf.append(data) - write('ctx_pop()') + write('context.pop()') write('if 0: yield None') self.indention -= 1 write('yield ' + self.handle_call_func(node.expr, @@ -870,12 +856,12 @@ class PythonTranslator(Translator): write = lambda x: buf.append(self.indent(x)) write('def filtered():') self.indention += 1 - write('ctx_push()') + write('context.push()') write(self.nodeinfo(node.body)) data = self.handle_node(node.body) if data: buf.append(data) - write('ctx_pop()') + write('context.pop()') write('if 0: yield None') self.indention -= 1 write('yield %s' % self.filter('buffereater(filtered)()', @@ -898,13 +884,13 @@ class PythonTranslator(Translator): write = lambda x: buf.append(self.indent(x)) write(self.nodeinfo(node)) - write('ctx_push({\'super\': SuperBlock(%r, blocks, %r, context)})' % ( + write('context.push({\'super\': SuperBlock(%r, blocks, %r, context)})' % ( str(node.name), level )) write(self.nodeinfo(node.body)) buf.append(rv) - write('ctx_pop()') + write('context.pop()') return '\n'.join(buf) def handle_include(self, node): diff --git a/tests/conftest.py b/tests/conftest.py index 651aca8b..e97e30cf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,6 +17,40 @@ import py from jinja import Environment from jinja.parser import Parser +try: + # This code adds support for coverage.py (see + # http://nedbatchelder.com/code/modules/coverage.html). + # It prints a coverage report for the modules specified in all + # module globals (of the test modules) named "coverage_modules". + + import coverage, atexit + + IGNORED_MODULES = ['jinja._speedups', 'jinja.defaults', + 'jinja.translators'] + + def report_coverage(): + coverage.stop() + module_list = [ + mod for name, mod in sys.modules.copy().iteritems() if + getattr(mod, '__file__', None) and + name.startswith('jinja.') and + name not in IGNORED_MODULES + ] + module_list.sort() + coverage.report(module_list) + + def callback(option, opt_str, value, parser): + atexit.register(report_coverage) + coverage.erase() + coverage.start() + + py.test.config.addoptions('Test options', py.test.config.Option('-C', + '--coverage', action='callback', callback=callback, + help='Output information about code coverage (slow!)')) + +except ImportError: + coverage = None + class GlobalLoader(object): @@ -45,10 +79,12 @@ class Module(py.test.collect.Module): self.env = simple_env super(Module, self).__init__(*args, **kwargs) - def join(self, name): - obj = getattr(self.obj, name) - if hasattr(obj, 'func_code'): - return JinjaTestFunction(name, parent=self) + def makeitem(self, name, obj, usefilters=True): + if name.startswith('test_'): + if hasattr(obj, 'func_code'): + return JinjaTestFunction(name, parent=self) + elif isinstance(obj, basestring): + return JinjaDocTest(name, parent=self) class JinjaTestFunction(py.test.collect.Function): @@ -60,3 +96,19 @@ class JinjaTestFunction(py.test.collect.Function): target(self.parent.env, *args) else: target(*args) + + +class JinjaDocTest(py.test.collect.Item): + + def run(self): + mod = py.std.types.ModuleType(self.name) + mod.__doc__ = self.obj + self.execute(mod) + + def execute(self, mod): + mod.env = self.parent.env + mod.MODULE = self.parent.obj + failed, tot = py.compat.doctest.testmod(mod, verbose=True) + if failed: + py.test.fail('doctest %s: %s failed out of %s' % ( + self.fspath, failed, tot)) diff --git a/tests/runtime/bigtable.py b/tests/runtime/bigtable.py index 50100613..b1576df3 100644 --- a/tests/runtime/bigtable.py +++ b/tests/runtime/bigtable.py @@ -13,8 +13,12 @@ import timeit import jdebug from StringIO import StringIO -from genshi.builder import tag -from genshi.template import MarkupTemplate +try: + from genshi.builder import tag + from genshi.template import MarkupTemplate + have_genshi = True +except ImportError: + have_genshi = False from jinja import Environment @@ -33,7 +37,11 @@ try: except ImportError: have_kid = False -from Cheetah.Template import Template as CheetahTemplate +try: + from Cheetah.Template import Template as CheetahTemplate + have_cheetah = True +except ImportError: + have_cheetah = False try: from mako.template import Template as MakoTemplate @@ -44,7 +52,8 @@ except ImportError: table = [dict(zip('abcdefghij', map(unicode,range(1, 11)))) for x in range(1000)] -genshi_tmpl = MarkupTemplate(""" +if have_genshi: + genshi_tmpl = MarkupTemplate("""
@@ -78,7 +87,8 @@ jinja_tmpl = Environment().from_string('''
''') -cheetah_tmpl = CheetahTemplate(''' +if have_cheetah: + cheetah_tmpl = CheetahTemplate(''' #for $row in $table @@ -116,6 +126,8 @@ def test_jinja(): def test_genshi(): """Genshi Templates""" + if not have_genshi: + return stream = genshi_tmpl.generate(table=table) stream.render('html', strip_whitespace=False) @@ -128,6 +140,8 @@ def test_kid(): def test_cheetah(): """Cheetah Templates""" + if not have_cheetah: + return cheetah_tmpl.respond() def test_mako(): diff --git a/tests/test_loaders.py b/tests/test_loaders.py index e52458c7..ad1e7b0d 100644 --- a/tests/test_loaders.py +++ b/tests/test_loaders.py @@ -7,6 +7,8 @@ :license: BSD, see LICENSE for more details. """ +import time +import tempfile from jinja import Environment, loaders from jinja.exceptions import TemplateNotFound @@ -24,6 +26,10 @@ function_loader = loaders.FunctionLoader({'justfunction.html': 'FOO'}.get) choice_loader = loaders.ChoiceLoader([dict_loader, package_loader]) +class FakeLoader(loaders.BaseLoader): + local_attr = 42 + + def test_dict_loader(): env = Environment(loader=dict_loader) tmpl = env.get_template('justdict.html') @@ -84,3 +90,57 @@ def test_function_loader(): pass else: raise AssertionError('expected template exception') + + +def test_loader_redirect(): + env = Environment(loader=FakeLoader()) + assert env.loader.local_attr == 42 + assert env.loader.get_source + assert env.loader.load + + +class MemcacheTestingLoader(loaders.CachedLoaderMixin, loaders.BaseLoader): + + def __init__(self, enable): + loaders.CachedLoaderMixin.__init__(self, enable, 40, None, True, 'foo') + self.times = {} + self.idx = 0 + + def touch(self, name): + self.times[name] = time.time() + + def get_source(self, environment, name, parent): + self.touch(name) + self.idx += 1 + return 'Template %s (%d)' % (name, self.idx) + + def check_source_changed(self, environment, name): + if name in self.times: + return self.times[name] + return -1 + + +memcache_env = Environment(loader=MemcacheTestingLoader(True)) +no_memcache_env = Environment(loader=MemcacheTestingLoader(False)) + + +test_memcaching = r''' +>>> not_caching = MODULE.no_memcache_env.loader +>>> caching = MODULE.memcache_env.loader +>>> touch = caching.touch + +>>> tmpl1 = not_caching.load('test.html') +>>> tmpl2 = not_caching.load('test.html') +>>> tmpl1 == tmpl2 +False + +>>> tmpl1 = caching.load('test.html') +>>> tmpl2 = caching.load('test.html') +>>> tmpl1 == tmpl2 +True + +>>> touch('test.html') +>>> tmpl2 = caching.load('test.html') +>>> tmpl1 == tmpl2 +False +''' diff --git a/tests/test_parser.py b/tests/test_parser.py new file mode 100644 index 00000000..d9d75c00 --- /dev/null +++ b/tests/test_parser.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +""" + unit test for the parser + ~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2007 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + +from jinja import Environment + +NO_VARIABLE_BLOCK = '''\ +{# i'm a freaking comment #}\ +{% if foo %}{% foo %}{% endif %} +{% for item in seq %}{% item %}{% endfor %} +{% trans foo %}foo is {% foo %}{% endtrans %} +{% trans foo %}one foo{% pluralize %}{% foo %} foos{% endtrans %}''' + +PHP_SYNTAX = '''\ +\ + + +''' + +ERB_SYNTAX = '''\ +<%# I'm a comment, I'm not interesting %>\ +<% for item in seq -%> + <%= item %> +<%- endfor %>''' + +COMMENT_SYNTAX = '''\ +\ + + ${item} +''' + +SMARTY_SYNTAX = '''\ +{* I'm a comment, I'm not interesting *}\ +{for item in seq-} + {item} +{-endfor}''' + + +def test_no_variable_block(): + env = Environment('{%', '%}', None, None) + tmpl = env.from_string(NO_VARIABLE_BLOCK) + assert tmpl.render(foo=42, seq=range(2)).splitlines() == [ + '42', + '01', + 'foo is 42', + '42 foos' + ] + + +def test_php_syntax(): + env = Environment('', '', '') + tmpl = env.from_string(PHP_SYNTAX) + assert tmpl.render(seq=range(5)) == '01234' + + +def test_erb_syntax(): + env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>') + tmpl = env.from_string(ERB_SYNTAX) + assert tmpl.render(seq=range(5)) == '01234' + + +def test_comment_syntax(): + env = Environment('', '${', '}', '') + tmpl = env.from_string(COMMENT_SYNTAX) + assert tmpl.render(seq=range(5)) == '01234' + + +def test_smarty_syntax(): + env = Environment('{', '}', '{', '}', '{*', '*}') + tmpl = env.from_string(SMARTY_SYNTAX) + assert tmpl.render(seq=range(5)) == '01234' diff --git a/tests/test_security.py b/tests/test_security.py new file mode 100644 index 00000000..5e0099d6 --- /dev/null +++ b/tests/test_security.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +""" + unit test for security features + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2007 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + + +class PrivateStuff(object): + bar = lambda self: 23 + foo = lambda self: 42 + foo.jinja_unsafe_call = True + + +class PublicStuff(object): + jinja_allowed_attributes = ['bar'] + bar = lambda self: 23 + foo = lambda self: 42 + + +test_unsafe = ''' +>>> env.from_string("{{ foo.foo() }}").render(foo=MODULE.PrivateStuff()) +u'' +>>> env.from_string("{{ foo.bar() }}").render(foo=MODULE.PrivateStuff()) +u'23' + +>>> env.from_string("{{ foo.foo() }}").render(foo=MODULE.PublicStuff()) +u'' +>>> env.from_string("{{ foo.bar() }}").render(foo=MODULE.PublicStuff()) +u'23' + +>>> env.from_string("{{ foo.__class__ }}").render(foo=42) +u'' + +>>> env.from_string("{{ foo.func_code }}").render(foo=lambda:None) +u'' +''' + + +test_restricted = ''' +>>> env.from_string("{% for item.attribute in seq %}...{% endfor %}") +Traceback (most recent call last): + ... +TemplateSyntaxError: can't assign to expression. (line 1) +''' diff --git a/tests/test_streaming.py b/tests/test_streaming.py new file mode 100644 index 00000000..709105ba --- /dev/null +++ b/tests/test_streaming.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" + unit test for streaming interface + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2007 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" + + +test_basic_streaming = r""" +>>> tmpl = env.from_string("") +>>> stream = tmpl.stream(seq=range(4)) +>>> stream.next() +u'' +""" + +test_buffered_streaming = r""" +>>> tmpl = env.from_string("") +>>> stream = tmpl.stream(seq=range(4)) +>>> stream.enable_buffering(size=3) +>>> stream.next() +u'' +""" + +test_streaming_behavior = r""" +>>> tmpl = env.from_string("") +>>> stream = tmpl.stream() +>>> stream.buffered +False +>>> stream.enable_buffering(20) +>>> stream.buffered +True +>>> stream.disable_buffering() +>>> stream.buffered +False +"""