1
# -*- coding: utf-8 -*-
3
jinja2.testsuite.lexnparse
4
~~~~~~~~~~~~~~~~~~~~~~~~~~
6
All the unittests regarding lexing, parsing and syntax.
8
:copyright: (c) 2010 by the Jinja Team.
9
:license: BSD, see LICENSE for more details.
13
from jinja2.testsuite import JinjaTestCase
15
from jinja2 import Environment, Template, TemplateSyntaxError, \
17
from jinja2._compat import next, iteritems, text_type, PY2
18
from jinja2.lexer import Token, TokenStream, TOKEN_EOF, \
19
TOKEN_BLOCK_BEGIN, TOKEN_BLOCK_END
24
# how does a string look like in jinja syntax?
26
def jinja_string_repr(string):
27
return repr(string)[1:]
29
jinja_string_repr = repr
32
class TokenStreamTestCase(JinjaTestCase):
33
test_tokens = [Token(1, TOKEN_BLOCK_BEGIN, ''),
34
Token(2, TOKEN_BLOCK_END, ''),
37
def test_simple(self):
38
ts = TokenStream(self.test_tokens, "foo", "bar")
39
assert ts.current.type is TOKEN_BLOCK_BEGIN
41
assert not bool(ts.eos)
43
assert ts.current.type is TOKEN_BLOCK_END
45
assert not bool(ts.eos)
47
assert ts.current.type is TOKEN_EOF
52
token_types = [t.type for t in TokenStream(self.test_tokens, "foo", "bar")]
53
assert token_types == ['block_begin', 'block_end', ]
56
class LexerTestCase(JinjaTestCase):
59
tmpl = env.from_string('{% raw %}foo{% endraw %}|'
60
'{%raw%}{{ bar }}|{% baz %}{% endraw %}')
61
assert tmpl.render() == 'foo|{{ bar }}|{% baz %}'
64
tmpl = env.from_string('1 {%- raw -%} 2 {%- endraw -%} 3')
65
assert tmpl.render() == '123'
67
def test_balancing(self):
68
env = Environment('{%', '%}', '${', '}')
69
tmpl = env.from_string('''{% for item in seq
70
%}${{'foo': item}|upper}{% endfor %}''')
71
assert tmpl.render(seq=list(range(3))) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}"
73
def test_comments(self):
74
env = Environment('<!--', '-->', '{', '}')
75
tmpl = env.from_string('''\
77
<!--- for item in seq -->
81
assert tmpl.render(seq=list(range(3))) == ("<ul>\n <li>0</li>\n "
82
"<li>1</li>\n <li>2</li>\n</ul>")
84
def test_string_escapes(self):
85
for char in u'\0', u'\u2668', u'\xe4', u'\t', u'\r', u'\n':
86
tmpl = env.from_string('{{ %s }}' % jinja_string_repr(char))
87
assert tmpl.render() == char
88
assert env.from_string('{{ "\N{HOT SPRINGS}" }}').render() == u'\u2668'
90
def test_bytefallback(self):
91
from pprint import pformat
92
tmpl = env.from_string(u'''{{ 'foo'|pprint }}|{{ 'bär'|pprint }}''')
93
assert tmpl.render() == pformat('foo') + '|' + pformat(u'bär')
95
def test_operators(self):
96
from jinja2.lexer import operators
97
for test, expect in iteritems(operators):
100
stream = env.lexer.tokenize('{{ %s }}' % test)
102
assert stream.current.type == expect
104
def test_normalizing(self):
105
for seq in '\r', '\r\n', '\n':
106
env = Environment(newline_sequence=seq)
107
tmpl = env.from_string('1\n2\r\n3\n4\n')
108
result = tmpl.render()
109
assert result.replace(seq, 'X') == '1X2X3X4'
111
def test_trailing_newline(self):
112
for keep in [True, False]:
113
env = Environment(keep_trailing_newline=keep)
114
for template,expected in [
117
('with\nnewline\n', {False: 'with\nnewline'}),
118
('with\nseveral\n\n\n', {False: 'with\nseveral\n\n'}),
120
tmpl = env.from_string(template)
121
expect = expected.get(keep, template)
122
result = tmpl.render()
123
assert result == expect, (keep, template, result, expect)
125
class ParserTestCase(JinjaTestCase):
127
def test_php_syntax(self):
128
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->')
129
tmpl = env.from_string('''\
130
<!-- I'm a comment, I'm not interesting -->\
131
<? for item in seq -?>
134
assert tmpl.render(seq=list(range(5))) == '01234'
136
def test_erb_syntax(self):
137
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>')
138
tmpl = env.from_string('''\
139
<%# I'm a comment, I'm not interesting %>\
140
<% for item in seq -%>
143
assert tmpl.render(seq=list(range(5))) == '01234'
145
def test_comment_syntax(self):
146
env = Environment('<!--', '-->', '${', '}', '<!--#', '-->')
147
tmpl = env.from_string('''\
148
<!--# I'm a comment, I'm not interesting -->\
149
<!-- for item in seq --->
152
assert tmpl.render(seq=list(range(5))) == '01234'
154
def test_balancing(self):
155
tmpl = env.from_string('''{{{'foo':'bar'}.foo}}''')
156
assert tmpl.render() == 'bar'
158
def test_start_comment(self):
159
tmpl = env.from_string('''{# foo comment
161
{% macro blub() %}foo{% endmacro %}
163
assert tmpl.render().strip() == 'foo'
165
def test_line_syntax(self):
166
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%')
167
tmpl = env.from_string('''\
168
<%# regular comment %>
172
assert [int(x.strip()) for x in tmpl.render(seq=list(range(5))).split()] == \
175
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##')
176
tmpl = env.from_string('''\
177
<%# regular comment %>
179
${item} ## the rest of the stuff
181
assert [int(x.strip()) for x in tmpl.render(seq=list(range(5))).split()] == \
184
def test_line_syntax_priority(self):
185
# XXX: why is the whitespace there in front of the newline?
186
env = Environment('{%', '%}', '${', '}', '/*', '*/', '##', '#')
187
tmpl = env.from_string('''\
189
I'm a multiline comment */
191
* ${item} # this is just extra stuff
193
assert tmpl.render(seq=[1, 2]).strip() == '* 1\n* 2'
194
env = Environment('{%', '%}', '${', '}', '/*', '*/', '#', '##')
195
tmpl = env.from_string('''\
197
I'm a multiline comment */
199
* ${item} ## this is just extra stuff
200
## extra stuff i just want to ignore
202
assert tmpl.render(seq=[1, 2]).strip() == '* 1\n\n* 2'
204
def test_error_messages(self):
205
def assert_error(code, expected):
208
except TemplateSyntaxError as e:
209
assert str(e) == expected, 'unexpected error message'
211
assert False, 'that was supposed to be an error'
213
assert_error('{% for item in seq %}...{% endif %}',
214
"Encountered unknown tag 'endif'. Jinja was looking "
215
"for the following tags: 'endfor' or 'else'. The "
216
"innermost block that needs to be closed is 'for'.")
217
assert_error('{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}',
218
"Encountered unknown tag 'endfor'. Jinja was looking for "
219
"the following tags: 'elif' or 'else' or 'endif'. The "
220
"innermost block that needs to be closed is 'if'.")
221
assert_error('{% if foo %}',
222
"Unexpected end of template. Jinja was looking for the "
223
"following tags: 'elif' or 'else' or 'endif'. The "
224
"innermost block that needs to be closed is 'if'.")
225
assert_error('{% for item in seq %}',
226
"Unexpected end of template. Jinja was looking for the "
227
"following tags: 'endfor' or 'else'. The innermost block "
228
"that needs to be closed is 'for'.")
229
assert_error('{% block foo-bar-baz %}',
230
"Block names in Jinja have to be valid Python identifiers "
231
"and may not contain hyphens, use an underscore instead.")
232
assert_error('{% unknown_tag %}',
233
"Encountered unknown tag 'unknown_tag'.")
236
class SyntaxTestCase(JinjaTestCase):
240
env.globals['foo'] = lambda a, b, c, e, g: a + b + c + e + g
241
tmpl = env.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}")
242
assert tmpl.render() == 'abdfh'
244
def test_slicing(self):
245
tmpl = env.from_string('{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}')
246
assert tmpl.render() == '[1, 2, 3]|[3, 2, 1]'
249
tmpl = env.from_string("{{ foo.bar }}|{{ foo['bar'] }}")
250
assert tmpl.render(foo={'bar': 42}) == '42|42'
252
def test_subscript(self):
253
tmpl = env.from_string("{{ foo[0] }}|{{ foo[-1] }}")
254
assert tmpl.render(foo=[0, 1, 2]) == '0|2'
256
def test_tuple(self):
257
tmpl = env.from_string('{{ () }}|{{ (1,) }}|{{ (1, 2) }}')
258
assert tmpl.render() == '()|(1,)|(1, 2)'
261
tmpl = env.from_string('{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}')
262
assert tmpl.render() == '1.5|8'
265
tmpl = env.from_string('{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}')
266
assert tmpl.render() == '1|1.5|1'
268
def test_unary(self):
269
tmpl = env.from_string('{{ +3 }}|{{ -3 }}')
270
assert tmpl.render() == '3|-3'
272
def test_concat(self):
273
tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}")
274
assert tmpl.render() == '[1, 2]foo'
276
def test_compare(self):
277
tmpl = env.from_string('{{ 1 > 0 }}|{{ 1 >= 1 }}|{{ 2 < 3 }}|'
278
'{{ 2 == 2 }}|{{ 1 <= 1 }}')
279
assert tmpl.render() == 'True|True|True|True|True'
282
tmpl = env.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}')
283
assert tmpl.render() == 'True|False'
285
def test_literals(self):
286
tmpl = env.from_string('{{ [] }}|{{ {} }}|{{ () }}')
287
assert tmpl.render().lower() == '[]|{}|()'
290
tmpl = env.from_string('{{ true and false }}|{{ false '
291
'or true }}|{{ not false }}')
292
assert tmpl.render() == 'False|True|True'
294
def test_grouping(self):
295
tmpl = env.from_string('{{ (true and false) or (false and true) and not false }}')
296
assert tmpl.render() == 'False'
298
def test_django_attr(self):
299
tmpl = env.from_string('{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}')
300
assert tmpl.render() == '1|1'
302
def test_conditional_expression(self):
303
tmpl = env.from_string('''{{ 0 if true else 1 }}''')
304
assert tmpl.render() == '0'
306
def test_short_conditional_expression(self):
307
tmpl = env.from_string('<{{ 1 if false }}>')
308
assert tmpl.render() == '<>'
310
tmpl = env.from_string('<{{ (1 if false).bar }}>')
311
self.assert_raises(UndefinedError, tmpl.render)
313
def test_filter_priority(self):
314
tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}')
315
assert tmpl.render() == 'FOOBAR'
317
def test_function_calls(self):
320
(True, '*foo, *bar'),
321
(True, '*foo, bar=42'),
322
(True, '**foo, *bar'),
323
(True, '**foo, bar'),
325
(False, 'foo, bar=42'),
326
(False, 'foo, bar=23, *args'),
327
(False, 'a, b=c, *d, **e'),
328
(False, '*foo, **bar')
330
for should_fail, sig in tests:
332
self.assert_raises(TemplateSyntaxError,
333
env.from_string, '{{ foo(%s) }}' % sig)
335
env.from_string('foo(%s)' % sig)
337
def test_tuple_expr(self):
344
'{% for foo, bar in seq %}...{% endfor %}',
345
'{% for x in foo, bar %}...{% endfor %}',
346
'{% for x in foo, %}...{% endfor %}'
348
assert env.from_string(tmpl)
350
def test_trailing_comma(self):
351
tmpl = env.from_string('{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}')
352
assert tmpl.render().lower() == '(1, 2)|[1, 2]|{1: 2}'
354
def test_block_end_name(self):
355
env.from_string('{% block foo %}...{% endblock foo %}')
356
self.assert_raises(TemplateSyntaxError, env.from_string,
357
'{% block x %}{% endblock y %}')
359
def test_constant_casing(self):
360
for const in True, False, None:
361
tmpl = env.from_string('{{ %s }}|{{ %s }}|{{ %s }}' % (
362
str(const), str(const).lower(), str(const).upper()
364
assert tmpl.render() == '%s|%s|' % (const, const)
366
def test_test_chaining(self):
367
self.assert_raises(TemplateSyntaxError, env.from_string,
368
'{{ foo is string is sequence }}')
369
assert env.from_string('{{ 42 is string or 42 is number }}'
372
def test_string_concatenation(self):
373
tmpl = env.from_string('{{ "foo" "bar" "baz" }}')
374
assert tmpl.render() == 'foobarbaz'
376
def test_notin(self):
378
tmpl = env.from_string('''{{ not 42 in bar }}''')
379
assert tmpl.render(bar=bar) == text_type(not 42 in bar)
381
def test_implicit_subscribed_tuple(self):
383
def __getitem__(self, x):
385
t = env.from_string('{{ foo[1, 2] }}')
386
assert t.render(foo=Foo()) == u'(1, 2)'
389
tmpl = env.from_string('{% raw %}{{ FOO }} and {% BAR %}{% endraw %}')
390
assert tmpl.render() == '{{ FOO }} and {% BAR %}'
392
def test_const(self):
393
tmpl = env.from_string('{{ true }}|{{ false }}|{{ none }}|'
394
'{{ none is defined }}|{{ missing is defined }}')
395
assert tmpl.render() == 'True|False|None|True|False'
397
def test_neg_filter_priority(self):
398
node = env.parse('{{ -1|foo }}')
399
assert isinstance(node.body[0].nodes[0], nodes.Filter)
400
assert isinstance(node.body[0].nodes[0].node, nodes.Neg)
402
def test_const_assign(self):
403
constass1 = '''{% set true = 42 %}'''
404
constass2 = '''{% for none in seq %}{% endfor %}'''
405
for tmpl in constass1, constass2:
406
self.assert_raises(TemplateSyntaxError, env.from_string, tmpl)
408
def test_localset(self):
409
tmpl = env.from_string('''{% set foo = 0 %}\
410
{% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
412
assert tmpl.render() == '0'
414
def test_parse_unary(self):
415
tmpl = env.from_string('{{ -foo["bar"] }}')
416
assert tmpl.render(foo={'bar': 42}) == '-42'
417
tmpl = env.from_string('{{ -foo["bar"]|abs }}')
418
assert tmpl.render(foo={'bar': 42}) == '42'
421
class LstripBlocksTestCase(JinjaTestCase):
423
def test_lstrip(self):
424
env = Environment(lstrip_blocks=True, trim_blocks=False)
425
tmpl = env.from_string(''' {% if True %}\n {% endif %}''')
426
assert tmpl.render() == "\n"
428
def test_lstrip_trim(self):
429
env = Environment(lstrip_blocks=True, trim_blocks=True)
430
tmpl = env.from_string(''' {% if True %}\n {% endif %}''')
431
assert tmpl.render() == ""
433
def test_no_lstrip(self):
434
env = Environment(lstrip_blocks=True, trim_blocks=False)
435
tmpl = env.from_string(''' {%+ if True %}\n {%+ endif %}''')
436
assert tmpl.render() == " \n "
438
def test_lstrip_endline(self):
439
env = Environment(lstrip_blocks=True, trim_blocks=False)
440
tmpl = env.from_string(''' hello{% if True %}\n goodbye{% endif %}''')
441
assert tmpl.render() == " hello\n goodbye"
443
def test_lstrip_inline(self):
444
env = Environment(lstrip_blocks=True, trim_blocks=False)
445
tmpl = env.from_string(''' {% if True %}hello {% endif %}''')
446
assert tmpl.render() == 'hello '
448
def test_lstrip_nested(self):
449
env = Environment(lstrip_blocks=True, trim_blocks=False)
450
tmpl = env.from_string(''' {% if True %}a {% if True %}b {% endif %}c {% endif %}''')
451
assert tmpl.render() == 'a b c '
453
def test_lstrip_left_chars(self):
454
env = Environment(lstrip_blocks=True, trim_blocks=False)
455
tmpl = env.from_string(''' abc {% if True %}
457
assert tmpl.render() == ' abc \n hello'
459
def test_lstrip_embeded_strings(self):
460
env = Environment(lstrip_blocks=True, trim_blocks=False)
461
tmpl = env.from_string(''' {% set x = " {% str %} " %}{{ x }}''')
462
assert tmpl.render() == ' {% str %} '
464
def test_lstrip_preserve_leading_newlines(self):
465
env = Environment(lstrip_blocks=True, trim_blocks=False)
466
tmpl = env.from_string('''\n\n\n{% set hello = 1 %}''')
467
assert tmpl.render() == '\n\n\n'
469
def test_lstrip_comment(self):
470
env = Environment(lstrip_blocks=True, trim_blocks=False)
471
tmpl = env.from_string(''' {# if True #}
474
assert tmpl.render() == '\nhello\n'
476
def test_lstrip_angle_bracket_simple(self):
477
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
478
lstrip_blocks=True, trim_blocks=True)
479
tmpl = env.from_string(''' <% if True %>hello <% endif %>''')
480
assert tmpl.render() == 'hello '
482
def test_lstrip_angle_bracket_comment(self):
483
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
484
lstrip_blocks=True, trim_blocks=True)
485
tmpl = env.from_string(''' <%# if True %>hello <%# endif %>''')
486
assert tmpl.render() == 'hello '
488
def test_lstrip_angle_bracket(self):
489
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
490
lstrip_blocks=True, trim_blocks=True)
491
tmpl = env.from_string('''\
492
<%# regular comment %>
493
<% for item in seq %>
494
${item} ## the rest of the stuff
496
assert tmpl.render(seq=range(5)) == \
497
''.join('%s\n' % x for x in range(5))
499
def test_lstrip_angle_bracket_compact(self):
500
env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##',
501
lstrip_blocks=True, trim_blocks=True)
502
tmpl = env.from_string('''\
505
${item} ## the rest of the stuff
507
assert tmpl.render(seq=range(5)) == \
508
''.join('%s\n' % x for x in range(5))
510
def test_php_syntax_with_manual(self):
511
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->',
512
lstrip_blocks=True, trim_blocks=True)
513
tmpl = env.from_string('''\
514
<!-- I'm a comment, I'm not interesting -->
515
<? for item in seq -?>
518
assert tmpl.render(seq=range(5)) == '01234'
520
def test_php_syntax(self):
521
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->',
522
lstrip_blocks=True, trim_blocks=True)
523
tmpl = env.from_string('''\
524
<!-- I'm a comment, I'm not interesting -->
525
<? for item in seq ?>
528
assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5))
530
def test_php_syntax_compact(self):
531
env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->',
532
lstrip_blocks=True, trim_blocks=True)
533
tmpl = env.from_string('''\
534
<!-- I'm a comment, I'm not interesting -->
538
assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5))
540
def test_erb_syntax(self):
541
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>',
542
lstrip_blocks=True, trim_blocks=True)
544
#for n,r in env.lexer.rules.iteritems():
546
#print env.lexer.rules['root'][0][0].pattern
547
#print "'%s'" % tmpl.render(seq=range(5))
548
tmpl = env.from_string('''\
549
<%# I'm a comment, I'm not interesting %>
550
<% for item in seq %>
554
assert tmpl.render(seq=range(5)) == ''.join(' %s\n' % x for x in range(5))
556
def test_erb_syntax_with_manual(self):
557
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>',
558
lstrip_blocks=True, trim_blocks=True)
559
tmpl = env.from_string('''\
560
<%# I'm a comment, I'm not interesting %>
561
<% for item in seq -%>
564
assert tmpl.render(seq=range(5)) == '01234'
566
def test_erb_syntax_no_lstrip(self):
567
env = Environment('<%', '%>', '<%=', '%>', '<%#', '%>',
568
lstrip_blocks=True, trim_blocks=True)
569
tmpl = env.from_string('''\
570
<%# I'm a comment, I'm not interesting %>
571
<%+ for item in seq -%>
574
assert tmpl.render(seq=range(5)) == ' 01234'
576
def test_comment_syntax(self):
577
env = Environment('<!--', '-->', '${', '}', '<!--#', '-->',
578
lstrip_blocks=True, trim_blocks=True)
579
tmpl = env.from_string('''\
580
<!--# I'm a comment, I'm not interesting -->\
581
<!-- for item in seq --->
584
assert tmpl.render(seq=range(5)) == '01234'
587
suite = unittest.TestSuite()
588
suite.addTest(unittest.makeSuite(TokenStreamTestCase))
589
suite.addTest(unittest.makeSuite(LexerTestCase))
590
suite.addTest(unittest.makeSuite(ParserTestCase))
591
suite.addTest(unittest.makeSuite(SyntaxTestCase))
592
suite.addTest(unittest.makeSuite(LstripBlocksTestCase))