Skip to content

Commit 372c0a8

Browse files
hs-lsongclaude
andcommitted
Separate unit tests from performance tests for filter chain optimization
Moved unit tests to AstFilterChainTest.java, keeping only performance-related tests in AstFilterChainPerformanceTest.java for clearer test organization. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent a357765 commit 372c0a8

2 files changed

Lines changed: 116 additions & 120 deletions

File tree

src/test/java/com/hubspot/jinjava/el/ext/AstFilterChainPerformanceTest.java

Lines changed: 46 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@
1111
import org.junit.Test;
1212

1313
/**
14-
* Performance test to verify that the optimized filter chain performs better than
15-
* the nested method approach.
14+
* Performance tests for the filter chain optimization.
1615
*
17-
* Run with: mvn test -Dtest=AstFilterChainPerformanceTest
18-
* Or run the main() method directly for more detailed output.
16+
* Run manually with: mvn test -Dtest=AstFilterChainPerformanceTest
17+
* Or run the main() method directly for detailed output.
1918
*/
2019
public class AstFilterChainPerformanceTest {
2120

@@ -62,42 +61,69 @@ public void runPerformanceComparison() {
6261
System.out.println("=== Filter Chain Performance Test ===\n");
6362
System.out.println("Warming up...");
6463

65-
// Warmup
66-
runFilterTests(jinjavaOptimized, warmupIterations, false);
67-
runFilterTests(jinjavaUnoptimized, warmupIterations, false);
64+
runFilterTests(jinjavaOptimized, warmupIterations);
65+
runFilterTests(jinjavaUnoptimized, warmupIterations);
6866

6967
System.out.println(
7068
"Running performance tests with " + testIterations + " iterations each\n"
7169
);
7270

73-
// Single filter
7471
comparePerformance("Single filter: {{ name|trim }}", testIterations);
75-
76-
// Two chained filters
7772
comparePerformance("Two filters: {{ name|trim|lower }}", testIterations);
78-
79-
// Three chained filters
8073
comparePerformance("Three filters: {{ name|trim|lower|capitalize }}", testIterations);
81-
82-
// Five chained filters
8374
comparePerformance(
8475
"Five filters: {{ text|upper|replace('THE', 'a')|trim|lower|title }}",
8576
testIterations
8677
);
87-
88-
// Filter with arguments
8978
comparePerformance(
9079
"Filters with args: {{ text|truncate(20)|upper }}",
9180
testIterations
9281
);
93-
94-
// Multiple filter chains in same template
9582
comparePerformance(
9683
"Multiple chains: {{ name|trim|lower }} and {{ text|upper|truncate(10) }}",
9784
testIterations
9885
);
9986
}
10087

88+
@Test
89+
public void optimizedVersionShouldBeFaster() {
90+
int warmupIterations = 100;
91+
int testIterations = 1000;
92+
String template = "{{ content.text|upper|replace('THE', 'a')|trim|lower|title }}";
93+
94+
for (int i = 0; i < warmupIterations; i++) {
95+
jinjavaOptimized.render(template, context);
96+
jinjavaUnoptimized.render(template, context);
97+
}
98+
99+
long totalOptimizedTime = 0;
100+
long totalUnoptimizedTime = 0;
101+
int rounds = 3;
102+
103+
for (int round = 0; round < rounds; round++) {
104+
totalUnoptimizedTime += timeExecution(jinjavaUnoptimized, template, testIterations);
105+
totalOptimizedTime += timeExecution(jinjavaOptimized, template, testIterations);
106+
}
107+
108+
long avgUnoptimizedTime = totalUnoptimizedTime / rounds;
109+
long avgOptimizedTime = totalOptimizedTime / rounds;
110+
111+
System.out.printf(
112+
"Performance test: Optimized=%d ms, Unoptimized=%d ms, Speedup=%.2fx%n",
113+
avgOptimizedTime,
114+
avgUnoptimizedTime,
115+
(1.0 * avgUnoptimizedTime) / avgOptimizedTime
116+
);
117+
118+
assertThat(avgOptimizedTime)
119+
.as(
120+
"Optimized (%d ms) should be faster than unoptimized (%d ms)",
121+
avgOptimizedTime,
122+
avgUnoptimizedTime
123+
)
124+
.isLessThan((avgUnoptimizedTime * 95) / 100);
125+
}
126+
101127
private void comparePerformance(String description, int iterations) {
102128
String template = description.substring(description.indexOf("{{"));
103129
if (description.contains(":")) {
@@ -106,13 +132,10 @@ private void comparePerformance(String description, int iterations) {
106132

107133
System.out.println(description);
108134

109-
// Run optimized
110135
long optimizedTime = timeExecution(jinjavaOptimized, template, iterations);
111-
112-
// Run unoptimized
113136
long unoptimizedTime = timeExecution(jinjavaUnoptimized, template, iterations);
114137

115-
double speedup = (double) unoptimizedTime / optimizedTime;
138+
double speedup = (1.0 * unoptimizedTime) / optimizedTime;
116139
System.out.printf(
117140
" Optimized: %d ms, Unoptimized: %d ms, Speedup: %.2fx%n%n",
118141
optimizedTime,
@@ -129,7 +152,7 @@ private long timeExecution(Jinjava jinjava, String template, int iterations) {
129152
return System.currentTimeMillis() - startTime;
130153
}
131154

132-
private void runFilterTests(Jinjava jinjava, int iterations, boolean print) {
155+
private void runFilterTests(Jinjava jinjava, int iterations) {
133156
String[] templates = {
134157
"{{ name|trim }}",
135158
"{{ name|trim|lower }}",
@@ -144,101 +167,4 @@ private void runFilterTests(Jinjava jinjava, int iterations, boolean print) {
144167
}
145168
}
146169
}
147-
148-
@Test
149-
public void itProducesSameResultsWithAndWithoutOptimization() {
150-
String[] templates = {
151-
"{{ name|trim }}",
152-
"{{ name|trim|lower }}",
153-
"{{ name|trim|lower|capitalize }}",
154-
"{{ text|upper|replace('THE', 'a')|trim|lower|title }}",
155-
"{{ text|truncate(20)|upper }}",
156-
"{{ name|trim|lower }} and {{ text|upper|truncate(10) }}",
157-
"{{ items|join(', ')|upper }}",
158-
"{{ number|string|length }}",
159-
};
160-
161-
for (String template : templates) {
162-
String optimizedResult = jinjavaOptimized.render(template, context);
163-
String unoptimizedResult = jinjavaUnoptimized.render(template, context);
164-
assertThat(optimizedResult)
165-
.as("Template: " + template)
166-
.isEqualTo(unoptimizedResult);
167-
}
168-
}
169-
170-
@Test
171-
public void itHandlesSingleFilterWithOptimization() {
172-
String result = jinjavaOptimized.render("{{ name|trim }}", context);
173-
assertThat(result).isEqualTo("Hello World");
174-
}
175-
176-
@Test
177-
public void itHandlesChainedFiltersWithOptimization() {
178-
String result = jinjavaOptimized.render("{{ name|trim|lower }}", context);
179-
assertThat(result).isEqualTo("hello world");
180-
}
181-
182-
@Test
183-
public void itHandlesFiltersWithArgumentsWithOptimization() {
184-
String result = jinjavaOptimized.render("{{ text|truncate(20)|upper }}", context);
185-
assertThat(result).isNotEmpty();
186-
assertThat(result).isUpperCase();
187-
}
188-
189-
@Test
190-
public void itHandlesComplexFilterChainWithOptimization() {
191-
String result = jinjavaOptimized.render(
192-
"{{ text|upper|replace('THE', 'a')|trim|lower|capitalize }}",
193-
context
194-
);
195-
assertThat(result).isNotEmpty();
196-
}
197-
198-
/**
199-
* This test verifies that the optimized version is faster than the unoptimized version.
200-
* The optimization should provide a measurable speedup for chained filters.
201-
*/
202-
@Test
203-
public void optimizedVersionShouldBeFaster() {
204-
int warmupIterations = 100;
205-
int testIterations = 1000;
206-
String template = "{{ content.text|upper|replace('THE', 'a')|trim|lower|title }}";
207-
208-
// Warmup both to ensure JIT compilation
209-
for (int i = 0; i < warmupIterations; i++) {
210-
jinjavaOptimized.render(template, context);
211-
jinjavaUnoptimized.render(template, context);
212-
}
213-
214-
// Run multiple rounds to get more stable results
215-
long totalOptimizedTime = 0;
216-
long totalUnoptimizedTime = 0;
217-
int rounds = 3;
218-
219-
for (int round = 0; round < rounds; round++) {
220-
totalUnoptimizedTime += timeExecution(jinjavaUnoptimized, template, testIterations);
221-
totalOptimizedTime += timeExecution(jinjavaOptimized, template, testIterations);
222-
}
223-
224-
long avgUnoptimizedTime = totalUnoptimizedTime / rounds;
225-
long avgOptimizedTime = totalOptimizedTime / rounds;
226-
227-
System.out.printf(
228-
"Performance test: Optimized=%d ms, Unoptimized=%d ms, Speedup=%.2fx%n",
229-
avgOptimizedTime,
230-
avgUnoptimizedTime,
231-
(double) avgUnoptimizedTime / avgOptimizedTime
232-
);
233-
234-
// The optimized version should be faster (allow 10% margin for system variance)
235-
// If optimized takes more than 90% of unoptimized time, fail the test
236-
assertThat(avgOptimizedTime)
237-
.as(
238-
"Optimized (%d ms) should be faster than unoptimized (%d ms)",
239-
avgOptimizedTime,
240-
avgUnoptimizedTime
241-
)
242-
.isLessThan((long) (avgUnoptimizedTime * 0.95));
243-
}
244170
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.hubspot.jinjava.el.ext;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import com.hubspot.jinjava.Jinjava;
6+
import com.hubspot.jinjava.JinjavaConfig;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import org.junit.Before;
10+
import org.junit.Test;
11+
12+
public class AstFilterChainTest {
13+
14+
private Jinjava jinjava;
15+
private Map<String, Object> context;
16+
17+
@Before
18+
public void setup() {
19+
jinjava =
20+
new Jinjava(
21+
JinjavaConfig.newBuilder().withEnableFilterChainOptimization(true).build()
22+
);
23+
24+
context = new HashMap<>();
25+
context.put("name", " Hello World ");
26+
context.put("text", "the quick brown fox jumps over the lazy dog");
27+
context.put("number", 12345);
28+
context.put("items", new String[] { "apple", "banana", "cherry" });
29+
}
30+
31+
@Test
32+
public void itHandlesSingleFilter() {
33+
String result = jinjava.render("{{ name|trim }}", context);
34+
assertThat(result).isEqualTo("Hello World");
35+
}
36+
37+
@Test
38+
public void itHandlesChainedFilters() {
39+
String result = jinjava.render("{{ name|trim|lower }}", context);
40+
assertThat(result).isEqualTo("hello world");
41+
}
42+
43+
@Test
44+
public void itHandlesFiltersWithArguments() {
45+
String result = jinjava.render("{{ text|truncate(20)|upper }}", context);
46+
assertThat(result).isNotEmpty();
47+
assertThat(result).isUpperCase();
48+
}
49+
50+
@Test
51+
public void itHandlesComplexFilterChain() {
52+
String result = jinjava.render(
53+
"{{ text|upper|replace('THE', 'a')|trim|lower|capitalize }}",
54+
context
55+
);
56+
assertThat(result).isNotEmpty();
57+
}
58+
59+
@Test
60+
public void itHandlesFilterWithJoin() {
61+
String result = jinjava.render("{{ items|join(', ')|upper }}", context);
62+
assertThat(result).isEqualTo("APPLE, BANANA, CHERRY");
63+
}
64+
65+
@Test
66+
public void itHandlesFilterWithStringConversion() {
67+
String result = jinjava.render("{{ number|string|length }}", context);
68+
assertThat(result).isEqualTo("5");
69+
}
70+
}

0 commit comments

Comments
 (0)