blob: 4bb95e0240a021845ec3d9431f251bd023c0a1af [file] [log] [blame]
IRIS YANG31213572020-08-18 13:17:02 +00001import pytest
2
3from jinja2 import DictLoader
4from jinja2 import Environment
5from jinja2 import TemplateRuntimeError
6from jinja2 import TemplateSyntaxError
7from jinja2 import UndefinedError
8
9
10@pytest.fixture
11def env_trim():
12 return Environment(trim_blocks=True)
13
14
15class TestForLoop:
16 def test_simple(self, env):
17 tmpl = env.from_string("{% for item in seq %}{{ item }}{% endfor %}")
18 assert tmpl.render(seq=list(range(10))) == "0123456789"
19
20 def test_else(self, env):
21 tmpl = env.from_string("{% for item in seq %}XXX{% else %}...{% endfor %}")
22 assert tmpl.render() == "..."
23
24 def test_else_scoping_item(self, env):
25 tmpl = env.from_string("{% for item in [] %}{% else %}{{ item }}{% endfor %}")
26 assert tmpl.render(item=42) == "42"
27
28 def test_empty_blocks(self, env):
29 tmpl = env.from_string("<{% for item in seq %}{% else %}{% endfor %}>")
30 assert tmpl.render() == "<>"
31
32 def test_context_vars(self, env):
33 slist = [42, 24]
34 for seq in [slist, iter(slist), reversed(slist), (_ for _ in slist)]:
35 tmpl = env.from_string(
36 """{% for item in seq -%}
37 {{ loop.index }}|{{ loop.index0 }}|{{ loop.revindex }}|{{
38 loop.revindex0 }}|{{ loop.first }}|{{ loop.last }}|{{
39 loop.length }}###{% endfor %}"""
40 )
41 one, two, _ = tmpl.render(seq=seq).split("###")
42 (
43 one_index,
44 one_index0,
45 one_revindex,
46 one_revindex0,
47 one_first,
48 one_last,
49 one_length,
50 ) = one.split("|")
51 (
52 two_index,
53 two_index0,
54 two_revindex,
55 two_revindex0,
56 two_first,
57 two_last,
58 two_length,
59 ) = two.split("|")
60
61 assert int(one_index) == 1 and int(two_index) == 2
62 assert int(one_index0) == 0 and int(two_index0) == 1
63 assert int(one_revindex) == 2 and int(two_revindex) == 1
64 assert int(one_revindex0) == 1 and int(two_revindex0) == 0
65 assert one_first == "True" and two_first == "False"
66 assert one_last == "False" and two_last == "True"
67 assert one_length == two_length == "2"
68
69 def test_cycling(self, env):
70 tmpl = env.from_string(
71 """{% for item in seq %}{{
72 loop.cycle('<1>', '<2>') }}{% endfor %}{%
73 for item in seq %}{{ loop.cycle(*through) }}{% endfor %}"""
74 )
75 output = tmpl.render(seq=list(range(4)), through=("<1>", "<2>"))
76 assert output == "<1><2>" * 4
77
78 def test_lookaround(self, env):
79 tmpl = env.from_string(
80 """{% for item in seq -%}
81 {{ loop.previtem|default('x') }}-{{ item }}-{{
82 loop.nextitem|default('x') }}|
83 {%- endfor %}"""
84 )
85 output = tmpl.render(seq=list(range(4)))
86 assert output == "x-0-1|0-1-2|1-2-3|2-3-x|"
87
88 def test_changed(self, env):
89 tmpl = env.from_string(
90 """{% for item in seq -%}
91 {{ loop.changed(item) }},
92 {%- endfor %}"""
93 )
94 output = tmpl.render(seq=[None, None, 1, 2, 2, 3, 4, 4, 4])
95 assert output == "True,False,True,True,False,True,True,False,False,"
96
97 def test_scope(self, env):
98 tmpl = env.from_string("{% for item in seq %}{% endfor %}{{ item }}")
99 output = tmpl.render(seq=list(range(10)))
100 assert not output
101
102 def test_varlen(self, env):
103 tmpl = env.from_string("{% for item in iter %}{{ item }}{% endfor %}")
104 output = tmpl.render(iter=range(5))
105 assert output == "01234"
106
107 def test_noniter(self, env):
108 tmpl = env.from_string("{% for item in none %}...{% endfor %}")
109 pytest.raises(TypeError, tmpl.render)
110
111 def test_recursive(self, env):
112 tmpl = env.from_string(
113 """{% for item in seq recursive -%}
114 [{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
115 {%- endfor %}"""
116 )
117 assert (
118 tmpl.render(
119 seq=[
120 dict(a=1, b=[dict(a=1), dict(a=2)]),
121 dict(a=2, b=[dict(a=1), dict(a=2)]),
122 dict(a=3, b=[dict(a="a")]),
123 ]
124 )
125 == "[1<[1][2]>][2<[1][2]>][3<[a]>]"
126 )
127
128 def test_recursive_lookaround(self, env):
129 tmpl = env.from_string(
130 """{% for item in seq recursive -%}
131 [{{ loop.previtem.a if loop.previtem is defined else 'x' }}.{{
132 item.a }}.{{ loop.nextitem.a if loop.nextitem is defined else 'x'
133 }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
134 {%- endfor %}"""
135 )
136 assert (
137 tmpl.render(
138 seq=[
139 dict(a=1, b=[dict(a=1), dict(a=2)]),
140 dict(a=2, b=[dict(a=1), dict(a=2)]),
141 dict(a=3, b=[dict(a="a")]),
142 ]
143 )
144 == "[x.1.2<[x.1.2][1.2.x]>][1.2.3<[x.1.2][1.2.x]>][2.3.x<[x.a.x]>]"
145 )
146
147 def test_recursive_depth0(self, env):
148 tmpl = env.from_string(
149 """{% for item in seq recursive -%}
150 [{{ loop.depth0 }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
151 {%- endfor %}"""
152 )
153 assert (
154 tmpl.render(
155 seq=[
156 dict(a=1, b=[dict(a=1), dict(a=2)]),
157 dict(a=2, b=[dict(a=1), dict(a=2)]),
158 dict(a=3, b=[dict(a="a")]),
159 ]
160 )
161 == "[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]"
162 )
163
164 def test_recursive_depth(self, env):
165 tmpl = env.from_string(
166 """{% for item in seq recursive -%}
167 [{{ loop.depth }}:{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
168 {%- endfor %}"""
169 )
170 assert (
171 tmpl.render(
172 seq=[
173 dict(a=1, b=[dict(a=1), dict(a=2)]),
174 dict(a=2, b=[dict(a=1), dict(a=2)]),
175 dict(a=3, b=[dict(a="a")]),
176 ]
177 )
178 == "[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]"
179 )
180
181 def test_looploop(self, env):
182 tmpl = env.from_string(
183 """{% for row in table %}
184 {%- set rowloop = loop -%}
185 {% for cell in row -%}
186 [{{ rowloop.index }}|{{ loop.index }}]
187 {%- endfor %}
188 {%- endfor %}"""
189 )
190 assert tmpl.render(table=["ab", "cd"]) == "[1|1][1|2][2|1][2|2]"
191
192 def test_reversed_bug(self, env):
193 tmpl = env.from_string(
194 "{% for i in items %}{{ i }}"
195 "{% if not loop.last %}"
196 ",{% endif %}{% endfor %}"
197 )
198 assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3"
199
200 def test_loop_errors(self, env):
201 tmpl = env.from_string(
202 """{% for item in [1] if loop.index
203 == 0 %}...{% endfor %}"""
204 )
205 pytest.raises(UndefinedError, tmpl.render)
206 tmpl = env.from_string(
207 """{% for item in [] %}...{% else
208 %}{{ loop }}{% endfor %}"""
209 )
210 assert tmpl.render() == ""
211
212 def test_loop_filter(self, env):
213 tmpl = env.from_string(
214 "{% for item in range(10) if item is even %}[{{ item }}]{% endfor %}"
215 )
216 assert tmpl.render() == "[0][2][4][6][8]"
217 tmpl = env.from_string(
218 """
219 {%- for item in range(10) if item is even %}[{{
220 loop.index }}:{{ item }}]{% endfor %}"""
221 )
222 assert tmpl.render() == "[1:0][2:2][3:4][4:6][5:8]"
223
224 def test_loop_unassignable(self, env):
225 pytest.raises(
226 TemplateSyntaxError, env.from_string, "{% for loop in seq %}...{% endfor %}"
227 )
228
229 def test_scoped_special_var(self, env):
230 t = env.from_string(
231 "{% for s in seq %}[{{ loop.first }}{% for c in s %}"
232 "|{{ loop.first }}{% endfor %}]{% endfor %}"
233 )
234 assert t.render(seq=("ab", "cd")) == "[True|True|False][False|True|False]"
235
236 def test_scoped_loop_var(self, env):
237 t = env.from_string(
238 "{% for x in seq %}{{ loop.first }}"
239 "{% for y in seq %}{% endfor %}{% endfor %}"
240 )
241 assert t.render(seq="ab") == "TrueFalse"
242 t = env.from_string(
243 "{% for x in seq %}{% for y in seq %}"
244 "{{ loop.first }}{% endfor %}{% endfor %}"
245 )
246 assert t.render(seq="ab") == "TrueFalseTrueFalse"
247
248 def test_recursive_empty_loop_iter(self, env):
249 t = env.from_string(
250 """
251 {%- for item in foo recursive -%}{%- endfor -%}
252 """
253 )
254 assert t.render(dict(foo=[])) == ""
255
256 def test_call_in_loop(self, env):
257 t = env.from_string(
258 """
259 {%- macro do_something() -%}
260 [{{ caller() }}]
261 {%- endmacro %}
262
263 {%- for i in [1, 2, 3] %}
264 {%- call do_something() -%}
265 {{ i }}
266 {%- endcall %}
267 {%- endfor -%}
268 """
269 )
270 assert t.render() == "[1][2][3]"
271
272 def test_scoping_bug(self, env):
273 t = env.from_string(
274 """
275 {%- for item in foo %}...{{ item }}...{% endfor %}
276 {%- macro item(a) %}...{{ a }}...{% endmacro %}
277 {{- item(2) -}}
278 """
279 )
280 assert t.render(foo=(1,)) == "...1......2..."
281
282 def test_unpacking(self, env):
283 tmpl = env.from_string(
284 "{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}"
285 )
286 assert tmpl.render() == "1|2|3"
287
288 def test_intended_scoping_with_set(self, env):
289 tmpl = env.from_string(
290 "{% for item in seq %}{{ x }}{% set x = item %}{{ x }}{% endfor %}"
291 )
292 assert tmpl.render(x=0, seq=[1, 2, 3]) == "010203"
293
294 tmpl = env.from_string(
295 "{% set x = 9 %}{% for item in seq %}{{ x }}"
296 "{% set x = item %}{{ x }}{% endfor %}"
297 )
298 assert tmpl.render(x=0, seq=[1, 2, 3]) == "919293"
299
300
301class TestIfCondition:
302 def test_simple(self, env):
303 tmpl = env.from_string("""{% if true %}...{% endif %}""")
304 assert tmpl.render() == "..."
305
306 def test_elif(self, env):
307 tmpl = env.from_string(
308 """{% if false %}XXX{% elif true
309 %}...{% else %}XXX{% endif %}"""
310 )
311 assert tmpl.render() == "..."
312
313 def test_elif_deep(self, env):
314 elifs = "\n".join(f"{{% elif a == {i} %}}{i}" for i in range(1, 1000))
315 tmpl = env.from_string(f"{{% if a == 0 %}}0{elifs}{{% else %}}x{{% endif %}}")
316 for x in (0, 10, 999):
317 assert tmpl.render(a=x).strip() == str(x)
318 assert tmpl.render(a=1000).strip() == "x"
319
320 def test_else(self, env):
321 tmpl = env.from_string("{% if false %}XXX{% else %}...{% endif %}")
322 assert tmpl.render() == "..."
323
324 def test_empty(self, env):
325 tmpl = env.from_string("[{% if true %}{% else %}{% endif %}]")
326 assert tmpl.render() == "[]"
327
328 def test_complete(self, env):
329 tmpl = env.from_string(
330 "{% if a %}A{% elif b %}B{% elif c == d %}C{% else %}D{% endif %}"
331 )
332 assert tmpl.render(a=0, b=False, c=42, d=42.0) == "C"
333
334 def test_no_scope(self, env):
335 tmpl = env.from_string("{% if a %}{% set foo = 1 %}{% endif %}{{ foo }}")
336 assert tmpl.render(a=True) == "1"
337 tmpl = env.from_string("{% if true %}{% set foo = 1 %}{% endif %}{{ foo }}")
338 assert tmpl.render() == "1"
339
340
341class TestMacros:
342 def test_simple(self, env_trim):
343 tmpl = env_trim.from_string(
344 """\
345{% macro say_hello(name) %}Hello {{ name }}!{% endmacro %}
346{{ say_hello('Peter') }}"""
347 )
348 assert tmpl.render() == "Hello Peter!"
349
350 def test_scoping(self, env_trim):
351 tmpl = env_trim.from_string(
352 """\
353{% macro level1(data1) %}
354{% macro level2(data2) %}{{ data1 }}|{{ data2 }}{% endmacro %}
355{{ level2('bar') }}{% endmacro %}
356{{ level1('foo') }}"""
357 )
358 assert tmpl.render() == "foo|bar"
359
360 def test_arguments(self, env_trim):
361 tmpl = env_trim.from_string(
362 """\
363{% macro m(a, b, c='c', d='d') %}{{ a }}|{{ b }}|{{ c }}|{{ d }}{% endmacro %}
364{{ m() }}|{{ m('a') }}|{{ m('a', 'b') }}|{{ m(1, 2, 3) }}"""
365 )
366 assert tmpl.render() == "||c|d|a||c|d|a|b|c|d|1|2|3|d"
367
368 def test_arguments_defaults_nonsense(self, env_trim):
369 pytest.raises(
370 TemplateSyntaxError,
371 env_trim.from_string,
372 """\
373{% macro m(a, b=1, c) %}a={{ a }}, b={{ b }}, c={{ c }}{% endmacro %}""",
374 )
375
376 def test_caller_defaults_nonsense(self, env_trim):
377 pytest.raises(
378 TemplateSyntaxError,
379 env_trim.from_string,
380 """\
381{% macro a() %}{{ caller() }}{% endmacro %}
382{% call(x, y=1, z) a() %}{% endcall %}""",
383 )
384
385 def test_varargs(self, env_trim):
386 tmpl = env_trim.from_string(
387 """\
388{% macro test() %}{{ varargs|join('|') }}{% endmacro %}\
389{{ test(1, 2, 3) }}"""
390 )
391 assert tmpl.render() == "1|2|3"
392
393 def test_simple_call(self, env_trim):
394 tmpl = env_trim.from_string(
395 """\
396{% macro test() %}[[{{ caller() }}]]{% endmacro %}\
397{% call test() %}data{% endcall %}"""
398 )
399 assert tmpl.render() == "[[data]]"
400
401 def test_complex_call(self, env_trim):
402 tmpl = env_trim.from_string(
403 """\
404{% macro test() %}[[{{ caller('data') }}]]{% endmacro %}\
405{% call(data) test() %}{{ data }}{% endcall %}"""
406 )
407 assert tmpl.render() == "[[data]]"
408
409 def test_caller_undefined(self, env_trim):
410 tmpl = env_trim.from_string(
411 """\
412{% set caller = 42 %}\
413{% macro test() %}{{ caller is not defined }}{% endmacro %}\
414{{ test() }}"""
415 )
416 assert tmpl.render() == "True"
417
418 def test_include(self, env_trim):
419 env_trim = Environment(
420 loader=DictLoader(
421 {"include": "{% macro test(foo) %}[{{ foo }}]{% endmacro %}"}
422 )
423 )
424 tmpl = env_trim.from_string('{% from "include" import test %}{{ test("foo") }}')
425 assert tmpl.render() == "[foo]"
426
427 def test_macro_api(self, env_trim):
428 tmpl = env_trim.from_string(
429 "{% macro foo(a, b) %}{% endmacro %}"
430 "{% macro bar() %}{{ varargs }}{{ kwargs }}{% endmacro %}"
431 "{% macro baz() %}{{ caller() }}{% endmacro %}"
432 )
433 assert tmpl.module.foo.arguments == ("a", "b")
434 assert tmpl.module.foo.name == "foo"
435 assert not tmpl.module.foo.caller
436 assert not tmpl.module.foo.catch_kwargs
437 assert not tmpl.module.foo.catch_varargs
438 assert tmpl.module.bar.arguments == ()
439 assert not tmpl.module.bar.caller
440 assert tmpl.module.bar.catch_kwargs
441 assert tmpl.module.bar.catch_varargs
442 assert tmpl.module.baz.caller
443
444 def test_callself(self, env_trim):
445 tmpl = env_trim.from_string(
446 "{% macro foo(x) %}{{ x }}{% if x > 1 %}|"
447 "{{ foo(x - 1) }}{% endif %}{% endmacro %}"
448 "{{ foo(5) }}"
449 )
450 assert tmpl.render() == "5|4|3|2|1"
451
452 def test_macro_defaults_self_ref(self, env):
453 tmpl = env.from_string(
454 """
455 {%- set x = 42 %}
456 {%- macro m(a, b=x, x=23) %}{{ a }}|{{ b }}|{{ x }}{% endmacro -%}
457 """
458 )
459 assert tmpl.module.m(1) == "1||23"
460 assert tmpl.module.m(1, 2) == "1|2|23"
461 assert tmpl.module.m(1, 2, 3) == "1|2|3"
462 assert tmpl.module.m(1, x=7) == "1|7|7"
463
464
465class TestSet:
466 def test_normal(self, env_trim):
467 tmpl = env_trim.from_string("{% set foo = 1 %}{{ foo }}")
468 assert tmpl.render() == "1"
469 assert tmpl.module.foo == 1
470
471 def test_block(self, env_trim):
472 tmpl = env_trim.from_string("{% set foo %}42{% endset %}{{ foo }}")
473 assert tmpl.render() == "42"
474 assert tmpl.module.foo == "42"
475
476 def test_block_escaping(self):
477 env = Environment(autoescape=True)
478 tmpl = env.from_string(
479 "{% set foo %}<em>{{ test }}</em>{% endset %}foo: {{ foo }}"
480 )
481 assert tmpl.render(test="<unsafe>") == "foo: <em>&lt;unsafe&gt;</em>"
482
483 def test_set_invalid(self, env_trim):
484 pytest.raises(
485 TemplateSyntaxError, env_trim.from_string, "{% set foo['bar'] = 1 %}"
486 )
487 tmpl = env_trim.from_string("{% set foo.bar = 1 %}")
488 exc_info = pytest.raises(TemplateRuntimeError, tmpl.render, foo={})
489 assert "non-namespace object" in exc_info.value.message
490
491 def test_namespace_redefined(self, env_trim):
492 tmpl = env_trim.from_string("{% set ns = namespace() %}{% set ns.bar = 'hi' %}")
493 exc_info = pytest.raises(TemplateRuntimeError, tmpl.render, namespace=dict)
494 assert "non-namespace object" in exc_info.value.message
495
496 def test_namespace(self, env_trim):
497 tmpl = env_trim.from_string(
498 "{% set ns = namespace() %}{% set ns.bar = '42' %}{{ ns.bar }}"
499 )
500 assert tmpl.render() == "42"
501
502 def test_namespace_block(self, env_trim):
503 tmpl = env_trim.from_string(
504 "{% set ns = namespace() %}{% set ns.bar %}42{% endset %}{{ ns.bar }}"
505 )
506 assert tmpl.render() == "42"
507
508 def test_init_namespace(self, env_trim):
509 tmpl = env_trim.from_string(
510 "{% set ns = namespace(d, self=37) %}"
511 "{% set ns.b = 42 %}"
512 "{{ ns.a }}|{{ ns.self }}|{{ ns.b }}"
513 )
514 assert tmpl.render(d={"a": 13}) == "13|37|42"
515
516 def test_namespace_loop(self, env_trim):
517 tmpl = env_trim.from_string(
518 "{% set ns = namespace(found=false) %}"
519 "{% for x in range(4) %}"
520 "{% if x == v %}"
521 "{% set ns.found = true %}"
522 "{% endif %}"
523 "{% endfor %}"
524 "{{ ns.found }}"
525 )
526 assert tmpl.render(v=3) == "True"
527 assert tmpl.render(v=4) == "False"
528
529 def test_namespace_macro(self, env_trim):
530 tmpl = env_trim.from_string(
531 "{% set ns = namespace() %}"
532 "{% set ns.a = 13 %}"
533 "{% macro magic(x) %}"
534 "{% set x.b = 37 %}"
535 "{% endmacro %}"
536 "{{ magic(ns) }}"
537 "{{ ns.a }}|{{ ns.b }}"
538 )
539 assert tmpl.render() == "13|37"
540
541 def test_block_escaping_filtered(self):
542 env = Environment(autoescape=True)
543 tmpl = env.from_string(
544 "{% set foo | trim %}<em>{{ test }}</em> {% endset %}foo: {{ foo }}"
545 )
546 assert tmpl.render(test="<unsafe>") == "foo: <em>&lt;unsafe&gt;</em>"
547
548 def test_block_filtered(self, env_trim):
549 tmpl = env_trim.from_string(
550 "{% set foo | trim | length | string %} 42 {% endset %}{{ foo }}"
551 )
552 assert tmpl.render() == "2"
553 assert tmpl.module.foo == "2"
554
555 def test_block_filtered_set(self, env_trim):
556 def _myfilter(val, arg):
557 assert arg == " xxx "
558 return val
559
560 env_trim.filters["myfilter"] = _myfilter
561 tmpl = env_trim.from_string(
562 '{% set a = " xxx " %}'
563 "{% set foo | myfilter(a) | trim | length | string %}"
564 ' {% set b = " yy " %} 42 {{ a }}{{ b }} '
565 "{% endset %}"
566 "{{ foo }}"
567 )
568 assert tmpl.render() == "11"
569 assert tmpl.module.foo == "11"
570
571
572class TestWith:
573 def test_with(self, env):
574 tmpl = env.from_string(
575 """\
576 {% with a=42, b=23 -%}
577 {{ a }} = {{ b }}
578 {% endwith -%}
579 {{ a }} = {{ b }}\
580 """
581 )
582 assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] == [
583 "42 = 23",
584 "1 = 2",
585 ]
586
587 def test_with_argument_scoping(self, env):
588 tmpl = env.from_string(
589 """\
590 {%- with a=1, b=2, c=b, d=e, e=5 -%}
591 {{ a }}|{{ b }}|{{ c }}|{{ d }}|{{ e }}
592 {%- endwith -%}
593 """
594 )
595 assert tmpl.render(b=3, e=4) == "1|2|3|4|5"