Skip to content

Commit db8a1c1

Browse files
authored
Merge pull request #1295 from HubSpot/3.0/nested-interp-off
[6] Jinjava 3.0: Disable nested interpretation by default
2 parents d502216 + f704595 commit db8a1c1

9 files changed

Lines changed: 351 additions & 190 deletions

src/main/java/com/hubspot/jinjava/JinjavaConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ default boolean isFailOnUnknownTokens() {
108108

109109
@Value.Default
110110
default boolean isNestedInterpretationEnabled() {
111-
return true;
111+
return false; // Default changed to false in 3.0
112112
}
113113

114114
@Value.Default

src/test/java/com/hubspot/jinjava/lib/expression/EagerExpressionStrategyTest.java

Lines changed: 73 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
import static org.assertj.core.api.Assertions.assertThat;
44

55
import com.google.common.collect.ImmutableMap;
6+
import com.google.common.collect.ImmutableSet;
7+
import com.hubspot.jinjava.Jinjava;
68
import com.hubspot.jinjava.JinjavaConfig;
79
import com.hubspot.jinjava.LegacyOverrides;
10+
import com.hubspot.jinjava.interpret.Context;
811
import com.hubspot.jinjava.interpret.Context.Library;
912
import com.hubspot.jinjava.interpret.DeferredValue;
1013
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
@@ -14,15 +17,24 @@
1417
import com.hubspot.jinjava.objects.collections.PyList;
1518
import com.hubspot.jinjava.tree.ExpressionNodeTest;
1619
import java.util.ArrayList;
17-
import java.util.Collections;
18-
import org.junit.After;
1920
import org.junit.Before;
2021
import org.junit.Test;
2122

2223
public class EagerExpressionStrategyTest extends ExpressionNodeTest {
2324

25+
private Jinjava jinjava;
26+
27+
class EagerExecutionModeNoRaw extends EagerExecutionMode {
28+
29+
@Override
30+
public boolean isPreserveRawTags() {
31+
return false; // So that we can run all the ExpressionNodeTest tests without having the extra `{% raw %}` tags inserted
32+
}
33+
}
34+
2435
@Before
2536
public void eagerSetup() throws Exception {
37+
jinjava = new Jinjava(JinjavaConfig.newBuilder().build());
2638
jinjava
2739
.getGlobalContext()
2840
.registerFunction(
@@ -35,51 +47,52 @@ public void eagerSetup() throws Exception {
3547
interpreter =
3648
new JinjavaInterpreter(
3749
jinjava,
38-
context,
50+
new Context(),
3951
JinjavaConfig
4052
.newBuilder()
53+
.withExecutionMode(new EagerExecutionModeNoRaw())
54+
.build()
55+
);
56+
nestedInterpreter =
57+
new JinjavaInterpreter(
58+
jinjava,
59+
interpreter.getContext(),
60+
JinjavaConfig
61+
.newBuilder()
62+
.withNestedInterpretationEnabled(true)
63+
.withLegacyOverrides(
64+
LegacyOverrides.newBuilder().withUsePyishObjectMapper(true).build()
65+
)
4166
.withExecutionMode(EagerExecutionMode.instance())
4267
.build()
4368
);
44-
JinjavaInterpreter.pushCurrent(interpreter);
45-
context.put("deferred", DeferredValue.instance());
46-
}
47-
48-
@After
49-
public void teardown() {
50-
JinjavaInterpreter.popCurrent();
69+
interpreter.getContext().put("deferred", DeferredValue.instance());
70+
nestedInterpreter.getContext().put("deferred", DeferredValue.instance());
5171
}
5272

5373
@Test
5474
public void itPreservesRawTags() {
5575
interpreter =
5676
new JinjavaInterpreter(
5777
jinjava,
58-
context,
78+
new Context(),
5979
JinjavaConfig
6080
.newBuilder()
61-
.withNestedInterpretationEnabled(false)
62-
.withLegacyOverrides(
63-
LegacyOverrides.newBuilder().withUsePyishObjectMapper(true).build()
64-
)
6581
.withExecutionMode(EagerExecutionMode.instance())
6682
.build()
6783
);
68-
JinjavaInterpreter.pushCurrent(interpreter);
69-
try {
70-
assertExpectedOutput(
71-
"{{ '{{ foo }}' }} {{ '{% something %}' }} {{ 'not needed' }}",
72-
"{% raw %}{{ foo }}{% endraw %} {% raw %}{% something %}{% endraw %} not needed"
73-
);
74-
} finally {
75-
JinjavaInterpreter.popCurrent();
76-
}
84+
assertExpectedOutput(
85+
interpreter,
86+
"{{ '{{ foo }}' }} {{ '{% something %}' }} {{ 'not needed' }}",
87+
"{% raw %}{{ foo }}{% endraw %} {% raw %}{% something %}{% endraw %} not needed"
88+
);
7789
}
7890

7991
@Test
8092
public void itPreservesRawTagsNestedInterpretation() {
81-
context.put("bar", "bar");
93+
nestedInterpreter.getContext().put("bar", "bar");
8294
assertExpectedOutput(
95+
nestedInterpreter,
8396
"{{ '{{ 12345 }}' }} {{ '{% print bar %}' }} {{ 'not needed' }}",
8497
"12345 bar not needed"
8598
);
@@ -88,24 +101,27 @@ public void itPreservesRawTagsNestedInterpretation() {
88101
@Test
89102
public void itPrependsMacro() {
90103
assertExpectedOutput(
104+
interpreter,
91105
"{% macro foo(bar) %} {{ bar }} {% endmacro %}{{ foo(deferred) }}",
92106
"{% macro foo(bar) %} {{ bar }} {% endmacro %}{{ foo(deferred) }}"
93107
);
94108
}
95109

96110
@Test
97111
public void itPrependsSet() {
98-
context.put("foo", new PyList(new ArrayList<>()));
112+
interpreter.getContext().put("foo", new PyList(new ArrayList<>()));
99113
assertExpectedOutput(
114+
interpreter,
100115
"{{ foo.append(deferred) }}",
101116
"{% set foo = [] %}{{ foo.append(deferred) }}"
102117
);
103118
}
104119

105120
@Test
106121
public void itDoesConcatenation() {
107-
context.put("foo", "y'all");
122+
interpreter.getContext().put("foo", "y'all");
108123
assertExpectedOutput(
124+
interpreter,
109125
"{{ 'oh, ' ~ foo ~ foo ~ ' toaster' }}",
110126
"oh, y'ally'all toaster"
111127
);
@@ -116,6 +132,7 @@ public void itHandlesQuotesLikeJinja() {
116132
// {{ 'a|\'|\\\'|\\\\\'|"|\"|\\"|\\\\"|a ' ~ " b|\"|\\\"|\\\\\"|'|\'|\\'|\\\\'|b" }}
117133
// --> a|'|\'|\\'|"|"|\"|\\"|a b|"|\"|\\"|'|'|\'|\\'|b
118134
assertExpectedOutput(
135+
interpreter,
119136
"{{ 'a|\\'|\\\\\\'|\\\\\\\\\\'|\"|\\\"|\\\\\"|\\\\\\\\\"|a ' " +
120137
"~ \" b|\\\"|\\\\\\\"|\\\\\\\\\\\"|'|\\'|\\\\'|\\\\\\\\'|b\" }}",
121138
"a|'|\\'|\\\\'|\"|\"|\\\"|\\\\\"|a b|\"|\\\"|\\\\\"|'|'|\\'|\\\\'|b"
@@ -125,6 +142,7 @@ public void itHandlesQuotesLikeJinja() {
125142
@Test
126143
public void itGoesIntoDeferredExecutionMode() {
127144
assertExpectedOutput(
145+
interpreter,
128146
"{{ is_deferred_execution_mode() }}" +
129147
"{% if deferred %}{{ is_deferred_execution_mode() }}{% endif %}" +
130148
"{{ is_deferred_execution_mode() }}",
@@ -135,6 +153,7 @@ public void itGoesIntoDeferredExecutionMode() {
135153
@Test
136154
public void itGoesIntoDeferredExecutionModeWithMacro() {
137155
assertExpectedOutput(
156+
interpreter,
138157
"{% macro def() %}{{ is_deferred_execution_mode() }}{% endmacro %}" +
139158
"{{ def() }}" +
140159
"{% if deferred %}{{ def() }}{% endif %}" +
@@ -145,20 +164,28 @@ public void itGoesIntoDeferredExecutionModeWithMacro() {
145164

146165
@Test
147166
public void itDoesNotGoIntoDeferredExecutionModeUnnecessarily() {
148-
assertExpectedOutput("{{ is_deferred_execution_mode() }}", "false");
167+
assertExpectedOutput(interpreter, "{{ is_deferred_execution_mode() }}", "false");
149168
interpreter.getContext().setDeferredExecutionMode(true);
150-
assertExpectedOutput("{{ is_deferred_execution_mode() }}", "true");
169+
assertExpectedOutput(interpreter, "{{ is_deferred_execution_mode() }}", "true");
151170
}
152171

153172
@Test
154173
public void itDoesNotNestedInterpretIfThereAreFakeNotes() {
155-
assertExpectedOutput("{{ '{#something_to_{{keep}}' }}", "{#something_to_{{keep}}");
174+
assertExpectedOutput(
175+
nestedInterpreter,
176+
"{{ '{#something_to_{{keep}}' }}",
177+
"{#something_to_{{keep}}"
178+
);
156179
}
157180

158181
@Test
159182
public void itDoesNotReconstructWithDoubleCurlyBraces() {
160183
interpreter.getContext().put("foo", ImmutableMap.of("foo", ImmutableMap.of()));
161-
assertExpectedOutput("{{ deferred ~ foo }}", "{{ deferred ~ {'foo': {} } }}");
184+
assertExpectedOutput(
185+
interpreter,
186+
"{{ deferred ~ foo }}",
187+
"{{ deferred ~ {'foo': {} } }}"
188+
);
162189
}
163190

164191
@Test
@@ -167,6 +194,7 @@ public void itDoesNotReconstructWithNestedDoubleCurlyBraces() {
167194
.getContext()
168195
.put("foo", ImmutableMap.of("foo", ImmutableMap.of("bar", ImmutableMap.of())));
169196
assertExpectedOutput(
197+
interpreter,
170198
"{{ deferred ~ foo }}",
171199
"{{ deferred ~ {'foo': {'bar': {} } } }}"
172200
);
@@ -175,6 +203,7 @@ public void itDoesNotReconstructWithNestedDoubleCurlyBraces() {
175203
@Test
176204
public void itDoesNotReconstructDirectlyWrittenWithDoubleCurlyBraces() {
177205
assertExpectedOutput(
206+
interpreter,
178207
"{{ deferred ~ {\n'foo': {\n'bar': deferred\n}\n}\n }}",
179208
"{{ deferred ~ {'foo': {'bar': deferred} } }}"
180209
);
@@ -184,6 +213,7 @@ public void itDoesNotReconstructDirectlyWrittenWithDoubleCurlyBraces() {
184213
public void itReconstructsWithNestedInterpretation() {
185214
interpreter.getContext().put("foo", "{{ print 'bar' }}");
186215
assertExpectedOutput(
216+
interpreter,
187217
"{{ deferred ~ foo }}",
188218
"{{ deferred ~ '{{ print \\'bar\\' }}' }}"
189219
);
@@ -192,18 +222,24 @@ public void itReconstructsWithNestedInterpretation() {
192222
@Test
193223
public void itDoesNotDoNestedInterpretationWithSyntaxErrors() {
194224
try (
195-
InterpreterScopeClosable c = interpreter.enterScope(
196-
ImmutableMap.of(Library.TAG, Collections.singleton("print"))
225+
InterpreterScopeClosable c = nestedInterpreter.enterScope(
226+
ImmutableMap.of(Library.TAG, ImmutableSet.of("print"))
197227
)
198228
) {
199-
interpreter.getContext().put("foo", "{% print 'bar' %}");
229+
nestedInterpreter.getContext().put("foo", "{% print 'bar' %}");
200230
// Rather than rendering this to an empty string
201-
assertThat(interpreter.render("{{ foo }}")).isEqualTo("{% print 'bar' %}");
231+
assertExpectedOutput(nestedInterpreter, "{{ foo }}", "{% print 'bar' %}");
202232
}
203233
}
204234

205-
private void assertExpectedOutput(String inputTemplate, String expectedOutput) {
206-
assertThat(interpreter.render(inputTemplate)).isEqualTo(expectedOutput);
235+
private void assertExpectedOutput(
236+
JinjavaInterpreter interpreter,
237+
String inputTemplate,
238+
String expectedOutput
239+
) {
240+
try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) {
241+
assertThat(a.value().render(inputTemplate)).isEqualTo(expectedOutput);
242+
}
207243
}
208244

209245
public static boolean isDeferredExecutionMode() {

src/test/java/com/hubspot/jinjava/lib/tag/MacroTagTest.java

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -282,38 +282,41 @@ public void itEnforcesMacroRecursionWithMaxDepth() throws IOException {
282282
.build()
283283
)
284284
.newInterpreter();
285-
JinjavaInterpreter.pushCurrent(interpreter);
286-
287-
try {
285+
try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) {
288286
String template = fixtureText("ending-recursion");
289287
String out = interpreter.render(template);
290288
assertThat(interpreter.getErrorsCopy().get(0).getMessage())
291289
.contains("Max recursion limit of 2 reached for macro 'hello'");
292290
assertThat(out).contains("Hello Hello");
293-
} finally {
294-
JinjavaInterpreter.popCurrent();
295291
}
296292
}
297293

298294
@Test
299295
public void itPreventsRecursionForMacroWithVar() {
300-
String jinja =
301-
"{%- macro func(var) %}" +
302-
"{%- for f in var %}" +
303-
"{{ f.val }}" +
304-
"{%- endfor %}" +
305-
"{%- endmacro %}" +
306-
"{%- set var = {" +
307-
" 'f' : {" +
308-
" 'val': '{{ self }}'," +
309-
" }" +
310-
"} %}" +
311-
"{% set self='{{var}}' %}" +
312-
"{{ func(var) }}" +
313-
"";
314-
Node node = new TreeParser(interpreter, jinja).buildTree();
315-
assertThat(interpreter.render(node))
316-
.isEqualTo("{'f': {'val': '{'f': {'val': '{{ self }}'} }'} }");
296+
interpreter =
297+
new Jinjava(
298+
JinjavaConfig.newBuilder().withNestedInterpretationEnabled(true).build()
299+
)
300+
.newInterpreter();
301+
try (var a = JinjavaInterpreter.closeablePushCurrent(interpreter).get()) {
302+
String jinja =
303+
"{%- macro func(var) %}" +
304+
"{%- for k,f in var.items() %}" +
305+
"{{ f.val }}" +
306+
"{%- endfor %}" +
307+
"{%- endmacro %}" +
308+
"{%- set var = {" +
309+
" 'f' : {" +
310+
" 'val': '{{ self }}'," +
311+
" }" +
312+
"} %}" +
313+
"{% set self='{{var}}' %}" +
314+
"{{ func(var) }}" +
315+
"";
316+
Node node = new TreeParser(interpreter, jinja).buildTree();
317+
assertThat(interpreter.render(node))
318+
.isEqualTo("{'f': {'val': '{'f': {'val': '{{ self }}'} }'} }");
319+
}
317320
}
318321

319322
@Test

src/test/java/com/hubspot/jinjava/lib/tag/eager/EagerExtendsTagTest.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,18 @@ public class EagerExtendsTagTest extends ExtendsTagTest {
2222

2323
@Before
2424
public void eagerSetup() {
25+
eagerSetup(false);
26+
}
27+
28+
void eagerSetup(boolean nestedInterpretation) {
29+
JinjavaInterpreter.popCurrent();
2530
interpreter =
2631
new JinjavaInterpreter(
2732
jinjava,
2833
context,
2934
JinjavaConfig
3035
.newBuilder()
36+
.withNestedInterpretationEnabled(nestedInterpretation)
3137
.withExecutionMode(EagerExecutionMode.instance())
3238
.withLegacyOverrides(
3339
LegacyOverrides.newBuilder().withUsePyishObjectMapper(true).build()
@@ -66,23 +72,38 @@ public void itDefersBlockInExtendsChildSecondPass() {
6672

6773
@Test
6874
public void itDefersSuperBlockWithDeferred() {
75+
eagerSetup(true);
6976
expectedTemplateInterpreter.assertExpectedOutputNonIdempotent(
7077
"defers-super-block-with-deferred"
7178
);
7279
}
7380

7481
@Test
7582
public void itDefersSuperBlockWithDeferredSecondPass() {
83+
eagerSetup(true);
7684
context.put("deferred", "Resolved now");
77-
expectedTemplateInterpreter.assertExpectedOutput(
78-
"defers-super-block-with-deferred.expected"
79-
);
80-
context.remove(RelativePathResolver.CURRENT_PATH_CONTEXT_KEY);
8185
expectedTemplateInterpreter.assertExpectedNonEagerOutput(
8286
"defers-super-block-with-deferred.expected"
8387
);
8488
}
8589

90+
@Test
91+
public void itDefersSuperBlockWithDeferredNestedInterp() {
92+
eagerSetup(true);
93+
expectedTemplateInterpreter.assertExpectedOutputNonIdempotent(
94+
"defers-super-block-with-deferred-nested-interp"
95+
);
96+
}
97+
98+
@Test
99+
public void itDefersSuperBlockWithDeferredNestedInterpSecondPass() {
100+
eagerSetup(true);
101+
context.put("deferred", "Resolved now");
102+
expectedTemplateInterpreter.assertExpectedOutput(
103+
"defers-super-block-with-deferred-nested-interp.expected"
104+
);
105+
}
106+
86107
@Test
87108
public void itReconstructsDeferredOutsideBlock() {
88109
expectedTemplateInterpreter.assertExpectedOutputNonIdempotent(

0 commit comments

Comments
 (0)